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.
- npcpy/__init__.py +0 -7
- npcpy/data/audio.py +16 -99
- npcpy/data/image.py +43 -42
- npcpy/data/load.py +83 -124
- npcpy/data/text.py +28 -28
- npcpy/data/video.py +8 -32
- npcpy/data/web.py +51 -23
- npcpy/ft/diff.py +110 -0
- npcpy/ft/ge.py +115 -0
- npcpy/ft/memory_trainer.py +171 -0
- npcpy/ft/model_ensembler.py +357 -0
- npcpy/ft/rl.py +360 -0
- npcpy/ft/sft.py +248 -0
- npcpy/ft/usft.py +128 -0
- npcpy/gen/audio_gen.py +24 -0
- npcpy/gen/embeddings.py +13 -13
- npcpy/gen/image_gen.py +262 -117
- npcpy/gen/response.py +615 -415
- npcpy/gen/video_gen.py +53 -7
- npcpy/llm_funcs.py +1869 -437
- npcpy/main.py +1 -1
- npcpy/memory/command_history.py +844 -510
- npcpy/memory/kg_vis.py +833 -0
- npcpy/memory/knowledge_graph.py +892 -1845
- npcpy/memory/memory_processor.py +81 -0
- npcpy/memory/search.py +188 -90
- npcpy/mix/debate.py +192 -3
- npcpy/npc_compiler.py +1672 -801
- npcpy/npc_sysenv.py +593 -1266
- npcpy/serve.py +3120 -0
- npcpy/sql/ai_function_tools.py +257 -0
- npcpy/sql/database_ai_adapters.py +186 -0
- npcpy/sql/database_ai_functions.py +163 -0
- npcpy/sql/model_runner.py +19 -19
- npcpy/sql/npcsql.py +706 -507
- npcpy/sql/sql_model_compiler.py +156 -0
- npcpy/tools.py +183 -0
- npcpy/work/plan.py +13 -279
- npcpy/work/trigger.py +3 -3
- npcpy-1.2.32.dist-info/METADATA +803 -0
- npcpy-1.2.32.dist-info/RECORD +54 -0
- npcpy/data/dataframes.py +0 -171
- npcpy/memory/deep_research.py +0 -125
- npcpy/memory/sleep.py +0 -557
- npcpy/modes/_state.py +0 -78
- npcpy/modes/alicanto.py +0 -1075
- npcpy/modes/guac.py +0 -785
- npcpy/modes/mcp_npcsh.py +0 -822
- npcpy/modes/npc.py +0 -213
- npcpy/modes/npcsh.py +0 -1158
- npcpy/modes/plonk.py +0 -409
- npcpy/modes/pti.py +0 -234
- npcpy/modes/serve.py +0 -1637
- npcpy/modes/spool.py +0 -312
- npcpy/modes/wander.py +0 -549
- npcpy/modes/yap.py +0 -572
- npcpy/npc_team/alicanto.npc +0 -2
- npcpy/npc_team/alicanto.png +0 -0
- npcpy/npc_team/assembly_lines/test_pipeline.py +0 -181
- npcpy/npc_team/corca.npc +0 -13
- npcpy/npc_team/foreman.npc +0 -7
- npcpy/npc_team/frederic.npc +0 -6
- npcpy/npc_team/frederic4.png +0 -0
- npcpy/npc_team/guac.png +0 -0
- npcpy/npc_team/jinxs/automator.jinx +0 -18
- npcpy/npc_team/jinxs/bash_executer.jinx +0 -31
- npcpy/npc_team/jinxs/calculator.jinx +0 -11
- npcpy/npc_team/jinxs/edit_file.jinx +0 -96
- npcpy/npc_team/jinxs/file_chat.jinx +0 -14
- npcpy/npc_team/jinxs/gui_controller.jinx +0 -28
- npcpy/npc_team/jinxs/image_generation.jinx +0 -29
- npcpy/npc_team/jinxs/internet_search.jinx +0 -30
- npcpy/npc_team/jinxs/local_search.jinx +0 -152
- npcpy/npc_team/jinxs/npcsh_executor.jinx +0 -31
- npcpy/npc_team/jinxs/python_executor.jinx +0 -8
- npcpy/npc_team/jinxs/screen_cap.jinx +0 -25
- npcpy/npc_team/jinxs/sql_executor.jinx +0 -33
- npcpy/npc_team/kadiefa.npc +0 -3
- npcpy/npc_team/kadiefa.png +0 -0
- npcpy/npc_team/npcsh.ctx +0 -9
- npcpy/npc_team/npcsh_sibiji.png +0 -0
- npcpy/npc_team/plonk.npc +0 -2
- npcpy/npc_team/plonk.png +0 -0
- npcpy/npc_team/plonkjr.npc +0 -2
- npcpy/npc_team/plonkjr.png +0 -0
- npcpy/npc_team/sibiji.npc +0 -5
- npcpy/npc_team/sibiji.png +0 -0
- npcpy/npc_team/spool.png +0 -0
- npcpy/npc_team/templates/analytics/celona.npc +0 -0
- npcpy/npc_team/templates/hr_support/raone.npc +0 -0
- npcpy/npc_team/templates/humanities/eriane.npc +0 -4
- npcpy/npc_team/templates/it_support/lineru.npc +0 -0
- npcpy/npc_team/templates/marketing/slean.npc +0 -4
- npcpy/npc_team/templates/philosophy/maurawa.npc +0 -0
- npcpy/npc_team/templates/sales/turnic.npc +0 -4
- npcpy/npc_team/templates/software/welxor.npc +0 -0
- npcpy/npc_team/yap.png +0 -0
- npcpy/routes.py +0 -958
- npcpy/work/mcp_helpers.py +0 -357
- npcpy/work/mcp_server.py +0 -194
- npcpy-1.0.26.data/data/npcpy/npc_team/alicanto.npc +0 -2
- npcpy-1.0.26.data/data/npcpy/npc_team/alicanto.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/automator.jinx +0 -18
- npcpy-1.0.26.data/data/npcpy/npc_team/bash_executer.jinx +0 -31
- npcpy-1.0.26.data/data/npcpy/npc_team/calculator.jinx +0 -11
- npcpy-1.0.26.data/data/npcpy/npc_team/celona.npc +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/corca.npc +0 -13
- npcpy-1.0.26.data/data/npcpy/npc_team/edit_file.jinx +0 -96
- npcpy-1.0.26.data/data/npcpy/npc_team/eriane.npc +0 -4
- npcpy-1.0.26.data/data/npcpy/npc_team/file_chat.jinx +0 -14
- npcpy-1.0.26.data/data/npcpy/npc_team/foreman.npc +0 -7
- npcpy-1.0.26.data/data/npcpy/npc_team/frederic.npc +0 -6
- npcpy-1.0.26.data/data/npcpy/npc_team/frederic4.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/guac.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/gui_controller.jinx +0 -28
- npcpy-1.0.26.data/data/npcpy/npc_team/image_generation.jinx +0 -29
- npcpy-1.0.26.data/data/npcpy/npc_team/internet_search.jinx +0 -30
- npcpy-1.0.26.data/data/npcpy/npc_team/kadiefa.npc +0 -3
- npcpy-1.0.26.data/data/npcpy/npc_team/kadiefa.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/lineru.npc +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/local_search.jinx +0 -152
- npcpy-1.0.26.data/data/npcpy/npc_team/maurawa.npc +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/npcsh.ctx +0 -9
- npcpy-1.0.26.data/data/npcpy/npc_team/npcsh_executor.jinx +0 -31
- npcpy-1.0.26.data/data/npcpy/npc_team/npcsh_sibiji.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/plonk.npc +0 -2
- npcpy-1.0.26.data/data/npcpy/npc_team/plonk.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/plonkjr.npc +0 -2
- npcpy-1.0.26.data/data/npcpy/npc_team/plonkjr.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/python_executor.jinx +0 -8
- npcpy-1.0.26.data/data/npcpy/npc_team/raone.npc +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/screen_cap.jinx +0 -25
- npcpy-1.0.26.data/data/npcpy/npc_team/sibiji.npc +0 -5
- npcpy-1.0.26.data/data/npcpy/npc_team/sibiji.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/slean.npc +0 -4
- npcpy-1.0.26.data/data/npcpy/npc_team/spool.png +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/sql_executor.jinx +0 -33
- npcpy-1.0.26.data/data/npcpy/npc_team/test_pipeline.py +0 -181
- npcpy-1.0.26.data/data/npcpy/npc_team/turnic.npc +0 -4
- npcpy-1.0.26.data/data/npcpy/npc_team/welxor.npc +0 -0
- npcpy-1.0.26.data/data/npcpy/npc_team/yap.png +0 -0
- npcpy-1.0.26.dist-info/METADATA +0 -827
- npcpy-1.0.26.dist-info/RECORD +0 -139
- npcpy-1.0.26.dist-info/entry_points.txt +0 -11
- /npcpy/{modes → ft}/__init__.py +0 -0
- {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/WHEEL +0 -0
- {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
|
42
|
+
query_embeddings=[embedded_search_term], n_results=top_k * 2
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
unique_docs = list(dict.fromkeys(docs_to_embed))
|
|
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:
|
|
77
|
+
if emb:
|
|
78
78
|
output_embeddings.append(emb)
|
|
79
79
|
unique_doc_indices.append(idx)
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
doc_embeddings = np.array(output_embeddings)
|
|
83
83
|
query_embedding = np.array(embedded_search_term)
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
if len(doc_embeddings) == 0:
|
|
87
87
|
raise ValueError("No valid document embeddings found")
|
|
88
88
|
|
|
89
|
-
|
|
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
|
-
|
|
93
|
+
|
|
94
94
|
if query_norm == 0:
|
|
95
95
|
raise ValueError("Query embedding is zero-length")
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
cosine_similarities = np.dot(doc_embeddings, query_embedding) / (
|
|
99
99
|
doc_norms.flatten() * query_norm
|
|
100
100
|
)
|
|
101
101
|
|
|
102
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
303
|
+
|
|
198
304
|
header = f"\n{BOLD}{BLUE}RAG Query: {RESET}{GREEN}{command}{RESET}\n"
|
|
199
305
|
|
|
200
|
-
|
|
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,
|
|
312
|
+
chroma_client=None,
|
|
207
313
|
|
|
208
|
-
docs_to_embed=file_contents,
|
|
314
|
+
docs_to_embed=file_contents,
|
|
209
315
|
top_k=top_k
|
|
210
316
|
)
|
|
211
317
|
|
|
212
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
360
|
+
|
|
255
361
|
try:
|
|
256
|
-
|
|
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
|
-
|
|
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
|
-
|
|
378
|
+
|
|
273
379
|
separator = f"{YELLOW}{'-' * 100}{RESET}\n"
|
|
274
380
|
|
|
275
|
-
|
|
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
|
-
|
|
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
|
-
|
|
396
|
+
|
|
291
397
|
text = text.replace('\n', ' ').strip()
|
|
292
398
|
snippet = text[:85] + ("..." if len(text) > 85 else "")
|
|
293
399
|
|
|
294
|
-
|
|
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
|
-
|
|
405
|
+
|
|
300
406
|
knowledge_results = header + separator + "\n".join(processed_texts)
|
|
301
407
|
render_markdown(f"KNOWLEDGE BASE: {knowledge_results}")
|
|
302
408
|
|
|
303
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
471
|
+
|
|
364
472
|
all_chunks = []
|
|
365
473
|
|
|
366
|
-
|
|
474
|
+
|
|
367
475
|
all_chunks.extend(words)
|
|
368
476
|
|
|
369
|
-
|
|
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
|
-
|
|
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
|
-
|
|
487
|
+
|
|
380
488
|
if len(words) > 1:
|
|
381
489
|
all_chunks.append(command)
|
|
382
490
|
|
|
383
|
-
|
|
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
|
-
|
|
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]
|
|
504
|
+
chunk_results[chunk] = results[:top_k]
|
|
398
505
|
all_results.extend(results[:top_k])
|
|
399
506
|
|
|
400
|
-
|
|
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
|
-
|
|
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):
|
|
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
|
-
|
|
553
|
+
|
|
450
554
|
plain_results = []
|
|
451
|
-
for i, result in enumerate(unique_results[:15], 1):
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
581
|
+
Here are the top unique matching items from the search:
|
|
478
582
|
|
|
479
583
|
{e}
|
|
480
584
|
|
|
481
|
-
Please analyze these results and
|
|
482
|
-
|
|
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
|
-
|
|
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
|
|
598
|
+
return {"output": f"Error executing brainblast command: {e}", "messages": kwargs.get('messages',[]) }
|