npcpy 1.0.26__py3-none-any.whl → 1.2.32__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.
Files changed (148) hide show
  1. npcpy/__init__.py +0 -7
  2. npcpy/data/audio.py +16 -99
  3. npcpy/data/image.py +43 -42
  4. npcpy/data/load.py +83 -124
  5. npcpy/data/text.py +28 -28
  6. npcpy/data/video.py +8 -32
  7. npcpy/data/web.py +51 -23
  8. npcpy/ft/diff.py +110 -0
  9. npcpy/ft/ge.py +115 -0
  10. npcpy/ft/memory_trainer.py +171 -0
  11. npcpy/ft/model_ensembler.py +357 -0
  12. npcpy/ft/rl.py +360 -0
  13. npcpy/ft/sft.py +248 -0
  14. npcpy/ft/usft.py +128 -0
  15. npcpy/gen/audio_gen.py +24 -0
  16. npcpy/gen/embeddings.py +13 -13
  17. npcpy/gen/image_gen.py +262 -117
  18. npcpy/gen/response.py +615 -415
  19. npcpy/gen/video_gen.py +53 -7
  20. npcpy/llm_funcs.py +1869 -437
  21. npcpy/main.py +1 -1
  22. npcpy/memory/command_history.py +844 -510
  23. npcpy/memory/kg_vis.py +833 -0
  24. npcpy/memory/knowledge_graph.py +892 -1845
  25. npcpy/memory/memory_processor.py +81 -0
  26. npcpy/memory/search.py +188 -90
  27. npcpy/mix/debate.py +192 -3
  28. npcpy/npc_compiler.py +1672 -801
  29. npcpy/npc_sysenv.py +593 -1266
  30. npcpy/serve.py +3120 -0
  31. npcpy/sql/ai_function_tools.py +257 -0
  32. npcpy/sql/database_ai_adapters.py +186 -0
  33. npcpy/sql/database_ai_functions.py +163 -0
  34. npcpy/sql/model_runner.py +19 -19
  35. npcpy/sql/npcsql.py +706 -507
  36. npcpy/sql/sql_model_compiler.py +156 -0
  37. npcpy/tools.py +183 -0
  38. npcpy/work/plan.py +13 -279
  39. npcpy/work/trigger.py +3 -3
  40. npcpy-1.2.32.dist-info/METADATA +803 -0
  41. npcpy-1.2.32.dist-info/RECORD +54 -0
  42. npcpy/data/dataframes.py +0 -171
  43. npcpy/memory/deep_research.py +0 -125
  44. npcpy/memory/sleep.py +0 -557
  45. npcpy/modes/_state.py +0 -78
  46. npcpy/modes/alicanto.py +0 -1075
  47. npcpy/modes/guac.py +0 -785
  48. npcpy/modes/mcp_npcsh.py +0 -822
  49. npcpy/modes/npc.py +0 -213
  50. npcpy/modes/npcsh.py +0 -1158
  51. npcpy/modes/plonk.py +0 -409
  52. npcpy/modes/pti.py +0 -234
  53. npcpy/modes/serve.py +0 -1637
  54. npcpy/modes/spool.py +0 -312
  55. npcpy/modes/wander.py +0 -549
  56. npcpy/modes/yap.py +0 -572
  57. npcpy/npc_team/alicanto.npc +0 -2
  58. npcpy/npc_team/alicanto.png +0 -0
  59. npcpy/npc_team/assembly_lines/test_pipeline.py +0 -181
  60. npcpy/npc_team/corca.npc +0 -13
  61. npcpy/npc_team/foreman.npc +0 -7
  62. npcpy/npc_team/frederic.npc +0 -6
  63. npcpy/npc_team/frederic4.png +0 -0
  64. npcpy/npc_team/guac.png +0 -0
  65. npcpy/npc_team/jinxs/automator.jinx +0 -18
  66. npcpy/npc_team/jinxs/bash_executer.jinx +0 -31
  67. npcpy/npc_team/jinxs/calculator.jinx +0 -11
  68. npcpy/npc_team/jinxs/edit_file.jinx +0 -96
  69. npcpy/npc_team/jinxs/file_chat.jinx +0 -14
  70. npcpy/npc_team/jinxs/gui_controller.jinx +0 -28
  71. npcpy/npc_team/jinxs/image_generation.jinx +0 -29
  72. npcpy/npc_team/jinxs/internet_search.jinx +0 -30
  73. npcpy/npc_team/jinxs/local_search.jinx +0 -152
  74. npcpy/npc_team/jinxs/npcsh_executor.jinx +0 -31
  75. npcpy/npc_team/jinxs/python_executor.jinx +0 -8
  76. npcpy/npc_team/jinxs/screen_cap.jinx +0 -25
  77. npcpy/npc_team/jinxs/sql_executor.jinx +0 -33
  78. npcpy/npc_team/kadiefa.npc +0 -3
  79. npcpy/npc_team/kadiefa.png +0 -0
  80. npcpy/npc_team/npcsh.ctx +0 -9
  81. npcpy/npc_team/npcsh_sibiji.png +0 -0
  82. npcpy/npc_team/plonk.npc +0 -2
  83. npcpy/npc_team/plonk.png +0 -0
  84. npcpy/npc_team/plonkjr.npc +0 -2
  85. npcpy/npc_team/plonkjr.png +0 -0
  86. npcpy/npc_team/sibiji.npc +0 -5
  87. npcpy/npc_team/sibiji.png +0 -0
  88. npcpy/npc_team/spool.png +0 -0
  89. npcpy/npc_team/templates/analytics/celona.npc +0 -0
  90. npcpy/npc_team/templates/hr_support/raone.npc +0 -0
  91. npcpy/npc_team/templates/humanities/eriane.npc +0 -4
  92. npcpy/npc_team/templates/it_support/lineru.npc +0 -0
  93. npcpy/npc_team/templates/marketing/slean.npc +0 -4
  94. npcpy/npc_team/templates/philosophy/maurawa.npc +0 -0
  95. npcpy/npc_team/templates/sales/turnic.npc +0 -4
  96. npcpy/npc_team/templates/software/welxor.npc +0 -0
  97. npcpy/npc_team/yap.png +0 -0
  98. npcpy/routes.py +0 -958
  99. npcpy/work/mcp_helpers.py +0 -357
  100. npcpy/work/mcp_server.py +0 -194
  101. npcpy-1.0.26.data/data/npcpy/npc_team/alicanto.npc +0 -2
  102. npcpy-1.0.26.data/data/npcpy/npc_team/alicanto.png +0 -0
  103. npcpy-1.0.26.data/data/npcpy/npc_team/automator.jinx +0 -18
  104. npcpy-1.0.26.data/data/npcpy/npc_team/bash_executer.jinx +0 -31
  105. npcpy-1.0.26.data/data/npcpy/npc_team/calculator.jinx +0 -11
  106. npcpy-1.0.26.data/data/npcpy/npc_team/celona.npc +0 -0
  107. npcpy-1.0.26.data/data/npcpy/npc_team/corca.npc +0 -13
  108. npcpy-1.0.26.data/data/npcpy/npc_team/edit_file.jinx +0 -96
  109. npcpy-1.0.26.data/data/npcpy/npc_team/eriane.npc +0 -4
  110. npcpy-1.0.26.data/data/npcpy/npc_team/file_chat.jinx +0 -14
  111. npcpy-1.0.26.data/data/npcpy/npc_team/foreman.npc +0 -7
  112. npcpy-1.0.26.data/data/npcpy/npc_team/frederic.npc +0 -6
  113. npcpy-1.0.26.data/data/npcpy/npc_team/frederic4.png +0 -0
  114. npcpy-1.0.26.data/data/npcpy/npc_team/guac.png +0 -0
  115. npcpy-1.0.26.data/data/npcpy/npc_team/gui_controller.jinx +0 -28
  116. npcpy-1.0.26.data/data/npcpy/npc_team/image_generation.jinx +0 -29
  117. npcpy-1.0.26.data/data/npcpy/npc_team/internet_search.jinx +0 -30
  118. npcpy-1.0.26.data/data/npcpy/npc_team/kadiefa.npc +0 -3
  119. npcpy-1.0.26.data/data/npcpy/npc_team/kadiefa.png +0 -0
  120. npcpy-1.0.26.data/data/npcpy/npc_team/lineru.npc +0 -0
  121. npcpy-1.0.26.data/data/npcpy/npc_team/local_search.jinx +0 -152
  122. npcpy-1.0.26.data/data/npcpy/npc_team/maurawa.npc +0 -0
  123. npcpy-1.0.26.data/data/npcpy/npc_team/npcsh.ctx +0 -9
  124. npcpy-1.0.26.data/data/npcpy/npc_team/npcsh_executor.jinx +0 -31
  125. npcpy-1.0.26.data/data/npcpy/npc_team/npcsh_sibiji.png +0 -0
  126. npcpy-1.0.26.data/data/npcpy/npc_team/plonk.npc +0 -2
  127. npcpy-1.0.26.data/data/npcpy/npc_team/plonk.png +0 -0
  128. npcpy-1.0.26.data/data/npcpy/npc_team/plonkjr.npc +0 -2
  129. npcpy-1.0.26.data/data/npcpy/npc_team/plonkjr.png +0 -0
  130. npcpy-1.0.26.data/data/npcpy/npc_team/python_executor.jinx +0 -8
  131. npcpy-1.0.26.data/data/npcpy/npc_team/raone.npc +0 -0
  132. npcpy-1.0.26.data/data/npcpy/npc_team/screen_cap.jinx +0 -25
  133. npcpy-1.0.26.data/data/npcpy/npc_team/sibiji.npc +0 -5
  134. npcpy-1.0.26.data/data/npcpy/npc_team/sibiji.png +0 -0
  135. npcpy-1.0.26.data/data/npcpy/npc_team/slean.npc +0 -4
  136. npcpy-1.0.26.data/data/npcpy/npc_team/spool.png +0 -0
  137. npcpy-1.0.26.data/data/npcpy/npc_team/sql_executor.jinx +0 -33
  138. npcpy-1.0.26.data/data/npcpy/npc_team/test_pipeline.py +0 -181
  139. npcpy-1.0.26.data/data/npcpy/npc_team/turnic.npc +0 -4
  140. npcpy-1.0.26.data/data/npcpy/npc_team/welxor.npc +0 -0
  141. npcpy-1.0.26.data/data/npcpy/npc_team/yap.png +0 -0
  142. npcpy-1.0.26.dist-info/METADATA +0 -827
  143. npcpy-1.0.26.dist-info/RECORD +0 -139
  144. npcpy-1.0.26.dist-info/entry_points.txt +0 -11
  145. /npcpy/{modes → ft}/__init__.py +0 -0
  146. {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/WHEEL +0 -0
  147. {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/licenses/LICENSE +0 -0
  148. {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,81 @@
1
+ from dataclasses import dataclass
2
+ from typing import List, Dict, Any, Optional
3
+ from datetime import datetime
4
+ import threading
5
+ import queue
6
+ import time
7
+
8
+ @dataclass
9
+ class MemoryItem:
10
+ message_id: str
11
+ conversation_id: str
12
+ npc: str
13
+ team: str
14
+ directory_path: str
15
+ content: str
16
+ context: str
17
+ model: str
18
+ provider: str
19
+
20
+ def memory_approval_ui(memories: List[Dict]) -> List[Dict]:
21
+ if not memories:
22
+ return []
23
+
24
+ print(f"\n📝 {len(memories)} memories ready for approval:")
25
+
26
+ approvals = []
27
+ for i, memory in enumerate(memories, 1):
28
+ print(f"\n--- Memory {i}/{len(memories)} ---")
29
+ print(f"NPC: {memory['npc']}")
30
+ content_preview = memory['content'][:200]
31
+ if len(memory['content']) > 200:
32
+ content_preview += '...'
33
+ print(f"Content: {content_preview}")
34
+
35
+ while True:
36
+ choice = input(
37
+ "(a)pprove, (r)eject, (e)dit, (s)kip | "
38
+ "(A)ll approve, (R)all reject, (S)all skip: "
39
+ ).strip().lower()
40
+
41
+ if choice == 'a':
42
+ approvals.append({
43
+ "memory_id": memory['memory_id'],
44
+ "decision": "human-approved"
45
+ })
46
+ break
47
+ elif choice == 'r':
48
+ approvals.append({
49
+ "memory_id": memory['memory_id'],
50
+ "decision": "human-rejected"
51
+ })
52
+ break
53
+ elif choice == 'e':
54
+ edited = input("Edit memory: ").strip()
55
+ if edited:
56
+ approvals.append({
57
+ "memory_id": memory['memory_id'],
58
+ "decision": "human-edited",
59
+ "final_memory": edited
60
+ })
61
+ break
62
+ elif choice == 's':
63
+ break
64
+ elif choice == 'A':
65
+ for remaining_memory in memories[i-1:]:
66
+ approvals.append({
67
+ "memory_id": remaining_memory['memory_id'],
68
+ "decision": "human-approved"
69
+ })
70
+ return approvals
71
+ elif choice == 'R':
72
+ for remaining_memory in memories[i-1:]:
73
+ approvals.append({
74
+ "memory_id": remaining_memory['memory_id'],
75
+ "decision": "human-rejected"
76
+ })
77
+ return approvals
78
+ elif choice == 'S':
79
+ return approvals
80
+
81
+ return approvals
npcpy/memory/search.py CHANGED
@@ -35,21 +35,21 @@ def search_similar_texts(
35
35
  embedded_search_term = get_ollama_embeddings([query], embedding_model)[0]
36
36
 
37
37
  if docs_to_embed is None:
38
- # Fetch from the database if no documents to embed are provided
38
+
39
39
  collection_name = f"{embedding_provider}_{embedding_model}_embeddings"
40
40
  collection = chroma_client.get_collection(collection_name)
41
41
  results = collection.query(
42
- query_embeddings=[embedded_search_term], n_results=top_k * 2 # Fetch more to account for filtering
42
+ query_embeddings=[embedded_search_term], n_results=top_k * 2
43
43
  )
44
44
 
45
- # Filter out duplicates while preserving order
45
+
46
46
  seen_texts = set()
47
47
  filtered_results = []
48
48
 
49
49
  for idx, (id, distance, document) in enumerate(zip(
50
50
  results["ids"][0], results["distances"][0], results["documents"][0]
51
51
  )):
52
- # Check if this is a command (starts with /) and if we've seen it before
52
+
53
53
  if document not in seen_texts:
54
54
  seen_texts.add(document)
55
55
  filtered_results.append({
@@ -58,7 +58,7 @@ def search_similar_texts(
58
58
  "text": document
59
59
  })
60
60
 
61
- # Break if we have enough unique results
61
+
62
62
  if len(filtered_results) >= top_k:
63
63
  break
64
64
 
@@ -66,40 +66,40 @@ def search_similar_texts(
66
66
 
67
67
  print(f"\nNumber of documents to embed: {len(docs_to_embed)}")
68
68
 
69
- # Get embeddings for provided documents - use np.unique to remove duplicates
70
- unique_docs = list(dict.fromkeys(docs_to_embed)) # Preserves order while removing duplicates
69
+
70
+ unique_docs = list(dict.fromkeys(docs_to_embed))
71
71
  raw_embeddings = get_ollama_embeddings(unique_docs, embedding_model)
72
72
 
73
73
  output_embeddings = []
74
74
  unique_doc_indices = []
75
75
 
76
76
  for idx, emb in enumerate(raw_embeddings):
77
- if emb: # Exclude any empty embeddings
77
+ if emb:
78
78
  output_embeddings.append(emb)
79
79
  unique_doc_indices.append(idx)
80
80
 
81
- # Convert to numpy arrays for calculations
81
+
82
82
  doc_embeddings = np.array(output_embeddings)
83
83
  query_embedding = np.array(embedded_search_term)
84
84
 
85
- # Check for zero-length embeddings
85
+
86
86
  if len(doc_embeddings) == 0:
87
87
  raise ValueError("No valid document embeddings found")
88
88
 
89
- # Normalize embeddings to avoid division by zeros
89
+
90
90
  doc_norms = np.linalg.norm(doc_embeddings, axis=1, keepdims=True)
91
91
  query_norm = np.linalg.norm(query_embedding)
92
92
 
93
- # Ensure no zero vectors are being used in cosine similarity
93
+
94
94
  if query_norm == 0:
95
95
  raise ValueError("Query embedding is zero-length")
96
96
 
97
- # Calculate cosine similarities
97
+
98
98
  cosine_similarities = np.dot(doc_embeddings, query_embedding) / (
99
99
  doc_norms.flatten() * query_norm
100
100
  )
101
101
 
102
- # Get indices of top K documents
102
+
103
103
  top_indices = np.argsort(cosine_similarities)[::-1][:top_k]
104
104
 
105
105
  return [
@@ -155,7 +155,7 @@ def execute_search_command(
155
155
  else:
156
156
  num_results = 5
157
157
 
158
- # remove the -p and provider from the command string
158
+
159
159
  command = command.replace(f"-p {provider}", "").replace(
160
160
  f"--provider {provider}", ""
161
161
  )
@@ -172,21 +172,127 @@ def execute_search_command(
172
172
  "messages": messages,
173
173
  "output": result[0] + f"\n\n\n Citation Links: {result[1]}",
174
174
  }
175
+
176
+
177
+
178
+ def get_facts_for_rag(
179
+ kuzu_db_path: str,
180
+ chroma_db_path: str,
181
+ query: str,
182
+ group_filters: Optional[List[str]] = None,
183
+ top_k: int = 10,
184
+ ) -> str:
185
+ """Get facts for RAG by combining vector and graph search
186
+
187
+ Args:
188
+ kuzu_db_path: Path to Kuzu graph database
189
+ chroma_db_path: Path to Chroma vector database
190
+ query: Search query
191
+ group_filters: Optional list of groups to filter by
192
+ top_k: Number of results to return
193
+ embedding_model: Model to use for embeddings
194
+ provider: Provider for embeddings
195
+
196
+ Returns:
197
+ Formatted context string with retrieved facts
198
+ """
199
+
200
+ kuzu_conn = init_db(kuzu_db_path)
201
+ chroma_client, chroma_collection = setup_chroma_db(
202
+ "knowledge_graph",
203
+ "Facts extracted from various sources",
204
+ chroma_db_path
205
+ )
206
+
207
+
208
+ results = hybrid_search_with_chroma(
209
+ kuzu_conn=kuzu_conn,
210
+ chroma_collection=chroma_collection,
211
+ query=query,
212
+ group_filter=group_filters,
213
+ top_k=top_k,
214
+ )
215
+
216
+
217
+ context = "Related facts:\n\n"
218
+
219
+
220
+ context += "Most relevant facts:\n"
221
+ vector_matches = [r for r in results if r["source"] == "vector_search"]
222
+ for i, item in enumerate(vector_matches):
223
+ context += f"{i+1}. {item['fact']}\n"
224
+
225
+
226
+ context += "\nRelated concepts:\n"
227
+ graph_matches = [r for r in results if r["source"] != "vector_search"]
228
+ for i, item in enumerate(graph_matches):
229
+ group = item["source"].replace("graph_relation_via_", "")
230
+ context += f"{i+1}. {item['fact']} (related via {group})\n"
231
+
232
+
233
+ kuzu_conn.close()
234
+
235
+ return context
236
+ def answer_with_rag(
237
+ query: str,
238
+ kuzu_db_path,
239
+ chroma_db_path,
240
+ model,
241
+ provider,
242
+ ) -> str:
243
+ """Answer a query using RAG with facts from the knowledge base
244
+
245
+ Args:
246
+ query: User query
247
+ kuzu_db_path: Path to Kuzu graph database
248
+ chroma_db_path: Path to Chroma vector database
249
+ model: LLM model to use
250
+ provider: LLM provider
251
+ embedding_model: Model to use for embeddings
252
+
253
+ Returns:
254
+ Answer from the model
255
+ """
256
+
257
+ context = get_facts_for_rag(
258
+ kuzu_db_path,
259
+ chroma_db_path,
260
+ query,
261
+ )
262
+
263
+
264
+ prompt = f"""
265
+ Answer this question based on the retrieved information.
266
+
267
+ Question: {query}
268
+
269
+ {context}
270
+
271
+ Please provide a comprehensive answer based on the facts above. If the information
272
+ doesn't contain a direct answer, please indicate that clearly but try to synthesize
273
+ from the available facts.
274
+ """
275
+
276
+
277
+ response = get_llm_response(prompt, model=model, provider=provider)
278
+
279
+ return response["response"]
280
+
281
+
175
282
  def execute_rag_command(
176
283
  command: str,
177
284
  vector_db_path: str,
178
285
  embedding_model: str,
179
286
  embedding_provider: str,
180
- messages=None,
181
287
  top_k: int = 15,
182
- file_contents=None, # List of file content chunks
288
+ file_contents=None,
183
289
  **kwargs
184
290
  ) -> dict:
185
291
  """
186
292
  Execute the RAG command with support for embedding generation.
187
293
  When file_contents is provided, it searches those instead of the database.
188
294
  """
189
- # ANSI color codes for terminal output
295
+
190
296
  BLUE = "\033[94m"
191
297
  GREEN = "\033[92m"
192
298
  YELLOW = "\033[93m"
@@ -194,44 +300,44 @@ def execute_rag_command(
194
300
  RESET = "\033[0m"
195
301
  BOLD = "\033[1m"
196
302
 
197
- # Format header
303
+
198
304
  header = f"\n{BOLD}{BLUE}RAG Query: {RESET}{GREEN}{command}{RESET}\n"
199
305
 
200
- # If we have file contents, search those instead of the database
306
+
201
307
  if file_contents and len(file_contents) > 0:
202
308
  similar_chunks = search_similar_texts(
203
309
  command,
204
310
  embedding_model,
205
311
  embedding_provider,
206
- chroma_client=None, # Not using ChromaDB when searching files
312
+ chroma_client=None,
207
313
 
208
- docs_to_embed=file_contents, # Pass file chunks directly to embed
314
+ docs_to_embed=file_contents,
209
315
  top_k=top_k
210
316
  )
211
317
 
212
- # Process the results for display
318
+
213
319
  file_info = f"{BOLD}{BLUE}Files Processed: {RESET}{YELLOW}{len(file_contents)}{RESET}\n"
214
320
  separator = f"{YELLOW}{'-' * 100}{RESET}\n"
215
321
 
216
- # Format chunk results for display
322
+
217
323
  chunk_results = []
218
324
  for i, chunk in enumerate(similar_chunks, 1):
219
325
  score = chunk['score']
220
326
  text = chunk['text']
221
327
 
222
- # Truncate for display if needed
328
+
223
329
  display_text = text[:150] + ("..." if len(text) > 150 else "")
224
330
  chunk_results.append(f"{BOLD}{i:2d}{RESET}. {CYAN}[{score:.2f}]{RESET} {display_text}")
225
331
 
226
- # Display the file search results
332
+
227
333
  file_results = header + file_info + separator + "\n".join(chunk_results)
228
334
  render_markdown(f"FILE SEARCH RESULTS:\n{file_results}")
229
335
 
230
- # Prepare the chunks for the prompt (plain text version)
336
+
231
337
  plain_chunks = [f"{i+1}. {chunk['text']}" for i, chunk in enumerate(similar_chunks)]
232
338
  plain_results = "\n\n".join(plain_chunks)
233
339
 
234
- # Build the prompt focusing on file contents
340
+
235
341
  prompt = f"""
236
342
  The user asked: {command}
237
343
 
@@ -239,28 +345,28 @@ def execute_rag_command(
239
345
 
240
346
  {plain_results}
241
347
 
242
- Please respond to the user query based on these file contents.
348
+ Please respond to the user query based on the above information, integrating the information in an additive way, attempting to always find some possible connection
349
+ between the results and the initial input. do not do this haphazardly, be creative yet cautious.
243
350
  """
244
351
 
245
- # Get LLM response
352
+
246
353
  response = get_llm_response(
247
354
  prompt,
248
- messages=messages,
249
355
  **kwargs
250
356
  )
251
357
  return response
252
358
 
253
359
  else:
254
- # No file contents, search the database instead
360
+
255
361
  try:
256
- # Setup ChromaDB connection
362
+
257
363
  chroma_client, chroma_collection = setup_chroma_db(
258
364
  f"{embedding_provider}_{embedding_model}_embeddings",
259
365
  "Conversation embeddings",
260
366
  vector_db_path
261
367
  )
262
368
 
263
- # Search for similar texts in the database
369
+
264
370
  similar_texts = search_similar_texts(
265
371
  command,
266
372
  embedding_model,
@@ -269,16 +375,16 @@ def execute_rag_command(
269
375
  top_k=top_k,
270
376
  )
271
377
 
272
- # Process the results for display
378
+
273
379
  separator = f"{YELLOW}{'-' * 100}{RESET}\n"
274
380
 
275
- # Format results
381
+
276
382
  processed_texts = []
277
383
  for i, similar_text in enumerate(similar_texts, 1):
278
384
  text = similar_text['text']
279
385
  score = similar_text['score']
280
386
 
281
- # Format timestamp if available
387
+
282
388
  timestamp_str = ""
283
389
  try:
284
390
  if 'id' in similar_text and '_' in similar_text['id']:
@@ -287,24 +393,24 @@ def execute_rag_command(
287
393
  except (IndexError, ValueError, TypeError):
288
394
  pass
289
395
 
290
- # Clean up the text
396
+
291
397
  text = text.replace('\n', ' ').strip()
292
398
  snippet = text[:85] + ("..." if len(text) > 85 else "")
293
399
 
294
- # Format with colors
400
+
295
401
  processed_texts.append(
296
402
  f"{BOLD}{i:2d}{RESET}. {CYAN}[{score:.2f}]{RESET} {snippet} {timestamp_str}"
297
403
  )
298
404
 
299
- # Combine for display
405
+
300
406
  knowledge_results = header + separator + "\n".join(processed_texts)
301
407
  render_markdown(f"KNOWLEDGE BASE: {knowledge_results}")
302
408
 
303
- # Prepare plain text for the prompt
409
+
304
410
  plain_texts = [f"{i+1}. {similar_texts[i]['text']}" for i in range(len(similar_texts))]
305
411
  plain_results = "\n\n".join(plain_texts)
306
412
 
307
- # Build the prompt
413
+
308
414
  prompt = f"""
309
415
  The user asked: {command}
310
416
 
@@ -312,34 +418,31 @@ def execute_rag_command(
312
418
 
313
419
  {plain_results}
314
420
 
315
- Please respond to the user query based on the above information.
421
+ Please respond to the user query based on the above information, integrating the information in an additive way, attempting to always find some possible connection
422
+ between the results and the initial input. do not do this haphazardly, be creative yet cautious.
316
423
  """
317
424
 
318
- # Get LLM response
425
+
319
426
  response = get_llm_response(
320
427
  prompt,
321
- messages=messages,
322
428
  **kwargs
323
429
  )
324
430
  return response
325
431
 
326
432
  except Exception as e:
327
433
  traceback.print_exc()
328
- return {"output": f"Error searching knowledge base: {e}", "messages": messages}
434
+ return {"output": f"Error searching knowledge base: {e}", "messages": kwargs.get('messages', [])}
329
435
 
330
436
 
331
437
  def execute_brainblast_command(
332
438
  command: str,
333
- command_history,
334
- messages=None,
335
- top_k: int = 5, # Fewer results per chunk to keep total manageable
336
439
  **kwargs
337
440
  ) -> dict:
338
441
  """
339
442
  Execute a comprehensive "brainblast" search on command history.
340
443
  Breaks the query into words and searches for combinations of those words.
341
444
  """
342
- # ANSI color codes for terminal output
445
+
343
446
  BLUE = "\033[94m"
344
447
  GREEN = "\033[92m"
345
448
  YELLOW = "\033[93m"
@@ -347,57 +450,61 @@ def execute_brainblast_command(
347
450
  RESET = "\033[0m"
348
451
  BOLD = "\033[1m"
349
452
 
350
- print(f"\nBrainblast Query: {command}")
453
+
454
+ if not 'command_history' in kwargs:
455
+ raise Exception('Command history must be passed as a kwarg to this function')
456
+ command_history = kwargs.get('command_history')
457
+ top_k = kwargs.get('top_k', 10)
458
+
459
+
351
460
 
352
- # Format header for display
353
461
  header = f"\n{BOLD}{BLUE}BRAINBLAST Query: {RESET}{GREEN}{command}{RESET}\n"
354
462
  separator = f"{YELLOW}{'-' * 100}{RESET}\n"
355
463
 
356
464
  try:
357
- # Split the command into words
465
+
358
466
  words = command.split()
359
467
 
360
468
  if not words:
361
469
  return {"output": "Please provide search terms to use brainblast.", "messages": messages or []}
362
470
 
363
- # Generate different chunk sizes for searching
471
+
364
472
  all_chunks = []
365
473
 
366
- # Add individual words
474
+
367
475
  all_chunks.extend(words)
368
476
 
369
- # Add pairs of words
477
+
370
478
  if len(words) >= 2:
371
479
  for i in range(len(words) - 1):
372
480
  all_chunks.append(f"{words[i]} {words[i+1]}")
373
481
 
374
- # Add groups of 4 words
482
+
375
483
  if len(words) >= 4:
376
484
  for i in range(len(words) - 3):
377
485
  all_chunks.append(f"{words[i]} {words[i+1]} {words[i+2]} {words[i+3]}")
378
486
 
379
- # Add the entire query
487
+
380
488
  if len(words) > 1:
381
489
  all_chunks.append(command)
382
490
 
383
- # Remove duplicates while preserving order
491
+
384
492
  unique_chunks = []
385
493
  for chunk in all_chunks:
386
494
  if chunk not in unique_chunks:
387
495
  unique_chunks.append(chunk)
388
496
 
389
- # Search for each chunk
497
+
390
498
  all_results = []
391
499
  chunk_results = {}
392
500
 
393
501
  for chunk in unique_chunks:
394
502
  results = command_history.search_conversations(chunk)
395
- print(results)
396
503
  if results:
397
- chunk_results[chunk] = results[:top_k] # Limit results per chunk
504
+ chunk_results[chunk] = results[:top_k]
398
505
  all_results.extend(results[:top_k])
399
506
 
400
- # Remove duplicate results while preserving order
507
+
401
508
  unique_results = []
402
509
  seen_ids = set()
403
510
  for result in all_results:
@@ -410,7 +517,7 @@ def execute_brainblast_command(
410
517
  result_message = f"No matches found for any combination of terms in: {command}"
411
518
  render_markdown(f"BRAINBLAST SEARCH: {header}{separator}{result_message}")
412
519
 
413
- # Get LLM response
520
+
414
521
  prompt = f"""
415
522
  The user asked for a brainblast search with: {command}
416
523
 
@@ -420,18 +527,18 @@ def execute_brainblast_command(
420
527
 
421
528
  response = get_llm_response(
422
529
  prompt,
423
- messages=messages,
530
+ messages=kwargs.get('messages'),
424
531
  **kwargs
425
532
  )
426
- return response
533
+ return {'output':response.get('response'), 'messages':response.get('messages') or []}
534
+
427
535
 
428
- # Process the results for display
429
536
  processed_chunks = []
430
537
  for chunk, results in chunk_results.items():
431
538
  if results:
432
539
  chunk_display = f"{BOLD}{BLUE}Results for '{chunk}':{RESET}\n"
433
540
 
434
- for i, result in enumerate(results[:3], 1): # Just show top 3 for each chunk
541
+ for i, result in enumerate(results[:3], 1):
435
542
  cmd = result.get('content', '')
436
543
  timestamp = result.get('timestamp', '')
437
544
 
@@ -442,29 +549,26 @@ def execute_brainblast_command(
442
549
 
443
550
  processed_chunks.append(chunk_display)
444
551
 
445
- # Display summarized chunk results
446
- chunks_output = header + separator + "\n".join(processed_chunks)
447
- render_markdown(f"BRAINBLAST SEARCH: {chunks_output}")
448
552
 
449
- # Prepare the consolidated results for the prompt
553
+
450
554
  plain_results = []
451
- for i, result in enumerate(unique_results[:15], 1): # Limit to 15 total unique results
452
- content = result.get('content', '')
555
+ for i, result in enumerate(unique_results[:15], 1):
556
+ content = result.get('content', '')[0:250]
453
557
  timestamp = result.get('timestamp', '')
454
558
  location = result.get('directory_path', '')
455
559
 
456
- # Format without ANSI colors
560
+
457
561
  plain_results.append(
458
562
  f"{i}. [{timestamp}] Command: {cmd}\n Location: {location}\n Output: {content[:150] + ('...' if len(content) > 150 else '')}"
459
563
  )
460
564
 
461
- # Summary of which terms matched what
565
+
462
566
  term_summary = []
463
567
  for chunk, results in chunk_results.items():
464
568
  if results:
465
569
  term_summary.append(f"Term '{chunk}' matched {len(results)} commands")
466
570
 
467
- # Build the prompt
571
+
468
572
  f=', '.join(term_summary)
469
573
  e="\n\n".join(plain_results)
470
574
  prompt = f"""
@@ -474,27 +578,21 @@ def execute_brainblast_command(
474
578
  Here's a summary of what matched:
475
579
  {f}
476
580
 
477
- Here are the top unique matching commands from the search:
581
+ Here are the top unique matching items from the search:
478
582
 
479
583
  {e}
480
584
 
481
- Please analyze these results and provide insights to the user. Look for:
482
- 1. Patterns in the commands they've used
483
- 2. Common themes or topics
484
- 3. Any interesting or unusual commands that might be useful
485
- 4. Suggestions for how the user might better leverage their command history
486
-
487
- Focus on being helpful and surfacing valuable insights from their command history.
585
+ Please analyze these results and attempt to generate some novel insight about them in one sentence. think outside the box.
586
+ Provide a summary as well.
488
587
  """
489
588
 
490
- # Get LLM response
491
589
  response = get_llm_response(
492
590
  prompt,
493
- messages=messages,
494
- **kwargs
591
+ **kwargs,
495
592
  )
496
- return response
593
+ return {"output": response.get('response'),
594
+ "messages": response.get('messages',[]) }
497
595
 
498
596
  except Exception as e:
499
597
  traceback.print_exc()
500
- return {"output": f"Error executing brainblast command: {e}", "messages": messages or []}
598
+ return {"output": f"Error executing brainblast command: {e}", "messages": kwargs.get('messages',[]) }