npcpy 1.2.18__tar.gz → 1.2.20__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {npcpy-1.2.18/npcpy.egg-info → npcpy-1.2.20}/PKG-INFO +1 -1
- npcpy-1.2.20/npcpy/ft/memory_trainer.py +171 -0
- npcpy-1.2.20/npcpy/memory/memory_processor.py +65 -0
- {npcpy-1.2.18 → npcpy-1.2.20/npcpy.egg-info}/PKG-INFO +1 -1
- {npcpy-1.2.18 → npcpy-1.2.20}/setup.py +1 -1
- npcpy-1.2.18/npcpy/ft/memory_trainer.py +0 -164
- npcpy-1.2.18/npcpy/memory/memory_processor.py +0 -155
- {npcpy-1.2.18 → npcpy-1.2.20}/LICENSE +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/MANIFEST.in +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/README.md +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/__init__.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/data/__init__.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/data/audio.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/data/data_models.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/data/image.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/data/load.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/data/text.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/data/video.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/data/web.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/ft/__init__.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/ft/diff.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/ft/ge.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/ft/rl.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/ft/sft.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/gen/__init__.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/gen/audio_gen.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/gen/embeddings.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/gen/image_gen.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/gen/response.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/gen/video_gen.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/llm_funcs.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/main.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/memory/__init__.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/memory/command_history.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/memory/kg_vis.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/memory/knowledge_graph.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/memory/search.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/mix/__init__.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/mix/debate.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/npc_compiler.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/npc_sysenv.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/npcs.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/serve.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/sql/__init__.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/sql/model_runner.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/sql/npcsql.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/tools.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/work/__init__.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/work/desktop.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/work/plan.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy/work/trigger.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy.egg-info/SOURCES.txt +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy.egg-info/dependency_links.txt +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy.egg-info/requires.txt +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/npcpy.egg-info/top_level.txt +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/setup.cfg +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/tests/test_audio.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/tests/test_command_history.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/tests/test_image.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/tests/test_llm_funcs.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/tests/test_load.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/tests/test_npc_compiler.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/tests/test_npcsql.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/tests/test_response.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/tests/test_serve.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/tests/test_text.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/tests/test_tools.py +0 -0
- {npcpy-1.2.18 → npcpy-1.2.20}/tests/test_web.py +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from torch.utils.data import Dataset
|
|
3
|
+
import torch
|
|
4
|
+
import torch.nn as nn
|
|
5
|
+
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from typing import List, Dict, Tuple
|
|
9
|
+
import random
|
|
10
|
+
|
|
11
|
+
class MemoryDataset(Dataset):
|
|
12
|
+
def __init__(self, examples: List[Dict], tokenizer, max_length=512):
|
|
13
|
+
self.examples = examples
|
|
14
|
+
self.tokenizer = tokenizer
|
|
15
|
+
self.max_length = max_length
|
|
16
|
+
|
|
17
|
+
def __len__(self):
|
|
18
|
+
return len(self.examples)
|
|
19
|
+
|
|
20
|
+
def __getitem__(self, idx):
|
|
21
|
+
example = self.examples[idx]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
text = f"Memory: {example['memory']}\nContext: {example.get('context', '')}"
|
|
25
|
+
|
|
26
|
+
encoding = self.tokenizer(
|
|
27
|
+
text,
|
|
28
|
+
truncation=True,
|
|
29
|
+
padding='max_length',
|
|
30
|
+
max_length=self.max_length,
|
|
31
|
+
return_tensors='pt'
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
'input_ids': encoding['input_ids'].flatten(),
|
|
36
|
+
'attention_mask': encoding['attention_mask'].flatten(),
|
|
37
|
+
'labels': torch.tensor(example['label'], dtype=torch.long)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class MemoryTrainer:
|
|
41
|
+
def __init__(self, model_name="google/gemma-2b", device="cpu"):
|
|
42
|
+
self.device = device
|
|
43
|
+
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
|
|
44
|
+
if self.tokenizer.pad_token is None:
|
|
45
|
+
self.tokenizer.pad_token = self.tokenizer.eos_token
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
self.model = AutoModelForSequenceClassification.from_pretrained(
|
|
49
|
+
model_name,
|
|
50
|
+
num_labels=3
|
|
51
|
+
).to(device)
|
|
52
|
+
|
|
53
|
+
def prepare_training_data(self, approved_memories: List[Dict],
|
|
54
|
+
rejected_memories: List[Dict]) -> List[Dict]:
|
|
55
|
+
"""Prepare training data from memory examples"""
|
|
56
|
+
examples = []
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
for memory in approved_memories:
|
|
60
|
+
examples.append({
|
|
61
|
+
"memory": memory.get("final_memory") or memory.get("initial_memory"),
|
|
62
|
+
"context": memory.get("context", ""),
|
|
63
|
+
"label": 1
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
for memory in rejected_memories:
|
|
68
|
+
examples.append({
|
|
69
|
+
"memory": memory.get("initial_memory"),
|
|
70
|
+
"context": memory.get("context", ""),
|
|
71
|
+
"label": 0
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
edited_examples = []
|
|
76
|
+
for memory in approved_memories[:len(rejected_memories)//2]:
|
|
77
|
+
if memory.get("final_memory") and memory.get("initial_memory"):
|
|
78
|
+
|
|
79
|
+
edited_examples.append({
|
|
80
|
+
"memory": memory.get("initial_memory"),
|
|
81
|
+
"context": memory.get("context", ""),
|
|
82
|
+
"label": 2
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
examples.extend(edited_examples)
|
|
86
|
+
random.shuffle(examples)
|
|
87
|
+
return examples
|
|
88
|
+
|
|
89
|
+
def train(self, approved_memories: List[Dict], rejected_memories: List[Dict],
|
|
90
|
+
output_dir: str = "./memory_model", epochs: int = 3):
|
|
91
|
+
"""Train the memory classification model"""
|
|
92
|
+
|
|
93
|
+
if len(approved_memories) < 10 or len(rejected_memories) < 10:
|
|
94
|
+
print("Not enough training data. Need at least 10 approved and 10 rejected memories.")
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
training_data = self.prepare_training_data(approved_memories, rejected_memories)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
split_idx = int(0.8 * len(training_data))
|
|
101
|
+
train_data = training_data[:split_idx]
|
|
102
|
+
val_data = training_data[split_idx:]
|
|
103
|
+
|
|
104
|
+
train_dataset = MemoryDataset(train_data, self.tokenizer)
|
|
105
|
+
val_dataset = MemoryDataset(val_data, self.tokenizer)
|
|
106
|
+
|
|
107
|
+
training_args = TrainingArguments(
|
|
108
|
+
output_dir=output_dir,
|
|
109
|
+
num_train_epochs=epochs,
|
|
110
|
+
per_device_train_batch_size=4,
|
|
111
|
+
per_device_eval_batch_size=4,
|
|
112
|
+
warmup_steps=100,
|
|
113
|
+
weight_decay=0.01,
|
|
114
|
+
logging_dir='./logs',
|
|
115
|
+
evaluation_strategy="epoch",
|
|
116
|
+
save_strategy="epoch",
|
|
117
|
+
load_best_model_at_end=True,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
trainer = Trainer(
|
|
121
|
+
model=self.model,
|
|
122
|
+
args=training_args,
|
|
123
|
+
train_dataset=train_dataset,
|
|
124
|
+
eval_dataset=val_dataset,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
trainer.train()
|
|
128
|
+
trainer.save_model()
|
|
129
|
+
self.tokenizer.save_pretrained(output_dir)
|
|
130
|
+
|
|
131
|
+
print(f"Model trained and saved to {output_dir}")
|
|
132
|
+
return True
|
|
133
|
+
|
|
134
|
+
def predict_memory_action(self, memory_content: str, context: str = "") -> Tuple[str, float]:
|
|
135
|
+
"""Predict what action to take on a memory"""
|
|
136
|
+
text = f"Memory: {memory_content}\nContext: {context}"
|
|
137
|
+
|
|
138
|
+
encoding = self.tokenizer(
|
|
139
|
+
text,
|
|
140
|
+
truncation=True,
|
|
141
|
+
padding=True,
|
|
142
|
+
max_length=512,
|
|
143
|
+
return_tensors='pt'
|
|
144
|
+
).to(self.device)
|
|
145
|
+
|
|
146
|
+
with torch.no_grad():
|
|
147
|
+
outputs = self.model(**encoding)
|
|
148
|
+
probabilities = torch.softmax(outputs.logits, dim=-1)
|
|
149
|
+
predicted_class = torch.argmax(probabilities, dim=-1).item()
|
|
150
|
+
confidence = probabilities[0][predicted_class].item()
|
|
151
|
+
|
|
152
|
+
actions = {0: "model-rejected", 1: "model-approved", 2: "needs-editing"}
|
|
153
|
+
return actions[predicted_class], confidence
|
|
154
|
+
|
|
155
|
+
def auto_approve_memory(self, memory_content: str, context: str = "",
|
|
156
|
+
confidence_threshold: float = 0.8) -> Dict:
|
|
157
|
+
"""Auto-approve memory if confidence is high enough"""
|
|
158
|
+
action, confidence = self.predict_memory_action(memory_content, context)
|
|
159
|
+
|
|
160
|
+
if confidence >= confidence_threshold:
|
|
161
|
+
return {"action": action, "confidence": confidence, "auto_processed": True}
|
|
162
|
+
else:
|
|
163
|
+
return {"action": "pending_approval", "confidence": confidence, "auto_processed": False}
|
|
164
|
+
except:
|
|
165
|
+
Dataset = None
|
|
166
|
+
nn = None
|
|
167
|
+
Trainer = None
|
|
168
|
+
TrainingArguments = None
|
|
169
|
+
|
|
170
|
+
MemoryDataset = None
|
|
171
|
+
MemoryTrainer = None
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
|
|
21
|
+
def memory_approval_ui(memories: List[Dict]) -> List[Dict]:
|
|
22
|
+
"""Simple CLI interface for memory approval"""
|
|
23
|
+
if not memories:
|
|
24
|
+
return []
|
|
25
|
+
|
|
26
|
+
print(f"\n📝 {len(memories)} memories ready for approval:")
|
|
27
|
+
|
|
28
|
+
approvals = []
|
|
29
|
+
for i, memory in enumerate(memories, 1):
|
|
30
|
+
print(f"\n--- Memory {i}/{len(memories)} ---")
|
|
31
|
+
print(f"NPC: {memory['npc']}")
|
|
32
|
+
print(f"Content: {memory['content'][:200]}{'...' if len(memory['content']) > 200 else ''}")
|
|
33
|
+
|
|
34
|
+
while True:
|
|
35
|
+
choice = input("(a)pprove, (r)eject, (e)dit, (s)kip, (q)uit, (A)pprove all: ").strip().lower()
|
|
36
|
+
|
|
37
|
+
if choice == 'a':
|
|
38
|
+
approvals.append({"memory_id": memory['memory_id'], "decision": "human-approved"})
|
|
39
|
+
break
|
|
40
|
+
elif choice == 'r':
|
|
41
|
+
approvals.append({"memory_id": memory['memory_id'], "decision": "human-rejected"})
|
|
42
|
+
break
|
|
43
|
+
elif choice == 'e':
|
|
44
|
+
edited = input("Edit memory: ").strip()
|
|
45
|
+
if edited:
|
|
46
|
+
approvals.append({
|
|
47
|
+
"memory_id": memory['memory_id'],
|
|
48
|
+
"decision": "human-edited",
|
|
49
|
+
"final_memory": edited
|
|
50
|
+
})
|
|
51
|
+
break
|
|
52
|
+
elif choice == 's':
|
|
53
|
+
break
|
|
54
|
+
elif choice == 'q':
|
|
55
|
+
return approvals
|
|
56
|
+
elif choice == 'A':
|
|
57
|
+
|
|
58
|
+
for remaining_memory in memories[i-1:]:
|
|
59
|
+
approvals.append({
|
|
60
|
+
"memory_id": remaining_memory['memory_id'],
|
|
61
|
+
"decision": "human-approved"
|
|
62
|
+
})
|
|
63
|
+
return approvals
|
|
64
|
+
|
|
65
|
+
return approvals
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import torch
|
|
2
|
-
import torch.nn as nn
|
|
3
|
-
try:
|
|
4
|
-
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
|
|
5
|
-
from torch.utils.data import Dataset
|
|
6
|
-
except:
|
|
7
|
-
pass
|
|
8
|
-
import json
|
|
9
|
-
from typing import List, Dict, Tuple
|
|
10
|
-
import random
|
|
11
|
-
|
|
12
|
-
class MemoryDataset(Dataset):
|
|
13
|
-
def __init__(self, examples: List[Dict], tokenizer, max_length=512):
|
|
14
|
-
self.examples = examples
|
|
15
|
-
self.tokenizer = tokenizer
|
|
16
|
-
self.max_length = max_length
|
|
17
|
-
|
|
18
|
-
def __len__(self):
|
|
19
|
-
return len(self.examples)
|
|
20
|
-
|
|
21
|
-
def __getitem__(self, idx):
|
|
22
|
-
example = self.examples[idx]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
text = f"Memory: {example['memory']}\nContext: {example.get('context', '')}"
|
|
26
|
-
|
|
27
|
-
encoding = self.tokenizer(
|
|
28
|
-
text,
|
|
29
|
-
truncation=True,
|
|
30
|
-
padding='max_length',
|
|
31
|
-
max_length=self.max_length,
|
|
32
|
-
return_tensors='pt'
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
'input_ids': encoding['input_ids'].flatten(),
|
|
37
|
-
'attention_mask': encoding['attention_mask'].flatten(),
|
|
38
|
-
'labels': torch.tensor(example['label'], dtype=torch.long)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
class MemoryTrainer:
|
|
42
|
-
def __init__(self, model_name="google/gemma-2b", device="cpu"):
|
|
43
|
-
self.device = device
|
|
44
|
-
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
|
|
45
|
-
if self.tokenizer.pad_token is None:
|
|
46
|
-
self.tokenizer.pad_token = self.tokenizer.eos_token
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
self.model = AutoModelForSequenceClassification.from_pretrained(
|
|
50
|
-
model_name,
|
|
51
|
-
num_labels=3
|
|
52
|
-
).to(device)
|
|
53
|
-
|
|
54
|
-
def prepare_training_data(self, approved_memories: List[Dict],
|
|
55
|
-
rejected_memories: List[Dict]) -> List[Dict]:
|
|
56
|
-
"""Prepare training data from memory examples"""
|
|
57
|
-
examples = []
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
for memory in approved_memories:
|
|
61
|
-
examples.append({
|
|
62
|
-
"memory": memory.get("final_memory") or memory.get("initial_memory"),
|
|
63
|
-
"context": memory.get("context", ""),
|
|
64
|
-
"label": 1
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
for memory in rejected_memories:
|
|
69
|
-
examples.append({
|
|
70
|
-
"memory": memory.get("initial_memory"),
|
|
71
|
-
"context": memory.get("context", ""),
|
|
72
|
-
"label": 0
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
edited_examples = []
|
|
77
|
-
for memory in approved_memories[:len(rejected_memories)//2]:
|
|
78
|
-
if memory.get("final_memory") and memory.get("initial_memory"):
|
|
79
|
-
|
|
80
|
-
edited_examples.append({
|
|
81
|
-
"memory": memory.get("initial_memory"),
|
|
82
|
-
"context": memory.get("context", ""),
|
|
83
|
-
"label": 2
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
examples.extend(edited_examples)
|
|
87
|
-
random.shuffle(examples)
|
|
88
|
-
return examples
|
|
89
|
-
|
|
90
|
-
def train(self, approved_memories: List[Dict], rejected_memories: List[Dict],
|
|
91
|
-
output_dir: str = "./memory_model", epochs: int = 3):
|
|
92
|
-
"""Train the memory classification model"""
|
|
93
|
-
|
|
94
|
-
if len(approved_memories) < 10 or len(rejected_memories) < 10:
|
|
95
|
-
print("Not enough training data. Need at least 10 approved and 10 rejected memories.")
|
|
96
|
-
return False
|
|
97
|
-
|
|
98
|
-
training_data = self.prepare_training_data(approved_memories, rejected_memories)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
split_idx = int(0.8 * len(training_data))
|
|
102
|
-
train_data = training_data[:split_idx]
|
|
103
|
-
val_data = training_data[split_idx:]
|
|
104
|
-
|
|
105
|
-
train_dataset = MemoryDataset(train_data, self.tokenizer)
|
|
106
|
-
val_dataset = MemoryDataset(val_data, self.tokenizer)
|
|
107
|
-
|
|
108
|
-
training_args = TrainingArguments(
|
|
109
|
-
output_dir=output_dir,
|
|
110
|
-
num_train_epochs=epochs,
|
|
111
|
-
per_device_train_batch_size=4,
|
|
112
|
-
per_device_eval_batch_size=4,
|
|
113
|
-
warmup_steps=100,
|
|
114
|
-
weight_decay=0.01,
|
|
115
|
-
logging_dir='./logs',
|
|
116
|
-
evaluation_strategy="epoch",
|
|
117
|
-
save_strategy="epoch",
|
|
118
|
-
load_best_model_at_end=True,
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
trainer = Trainer(
|
|
122
|
-
model=self.model,
|
|
123
|
-
args=training_args,
|
|
124
|
-
train_dataset=train_dataset,
|
|
125
|
-
eval_dataset=val_dataset,
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
trainer.train()
|
|
129
|
-
trainer.save_model()
|
|
130
|
-
self.tokenizer.save_pretrained(output_dir)
|
|
131
|
-
|
|
132
|
-
print(f"Model trained and saved to {output_dir}")
|
|
133
|
-
return True
|
|
134
|
-
|
|
135
|
-
def predict_memory_action(self, memory_content: str, context: str = "") -> Tuple[str, float]:
|
|
136
|
-
"""Predict what action to take on a memory"""
|
|
137
|
-
text = f"Memory: {memory_content}\nContext: {context}"
|
|
138
|
-
|
|
139
|
-
encoding = self.tokenizer(
|
|
140
|
-
text,
|
|
141
|
-
truncation=True,
|
|
142
|
-
padding=True,
|
|
143
|
-
max_length=512,
|
|
144
|
-
return_tensors='pt'
|
|
145
|
-
).to(self.device)
|
|
146
|
-
|
|
147
|
-
with torch.no_grad():
|
|
148
|
-
outputs = self.model(**encoding)
|
|
149
|
-
probabilities = torch.softmax(outputs.logits, dim=-1)
|
|
150
|
-
predicted_class = torch.argmax(probabilities, dim=-1).item()
|
|
151
|
-
confidence = probabilities[0][predicted_class].item()
|
|
152
|
-
|
|
153
|
-
actions = {0: "model-rejected", 1: "model-approved", 2: "needs-editing"}
|
|
154
|
-
return actions[predicted_class], confidence
|
|
155
|
-
|
|
156
|
-
def auto_approve_memory(self, memory_content: str, context: str = "",
|
|
157
|
-
confidence_threshold: float = 0.8) -> Dict:
|
|
158
|
-
"""Auto-approve memory if confidence is high enough"""
|
|
159
|
-
action, confidence = self.predict_memory_action(memory_content, context)
|
|
160
|
-
|
|
161
|
-
if confidence >= confidence_threshold:
|
|
162
|
-
return {"action": action, "confidence": confidence, "auto_processed": True}
|
|
163
|
-
else:
|
|
164
|
-
return {"action": "pending_approval", "confidence": confidence, "auto_processed": False}
|
|
@@ -1,155 +0,0 @@
|
|
|
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
|
-
class MemoryApprovalQueue:
|
|
21
|
-
def __init__(self, command_history):
|
|
22
|
-
self.command_history = command_history
|
|
23
|
-
self.pending_queue = queue.Queue()
|
|
24
|
-
self.approval_results = queue.Queue()
|
|
25
|
-
self.processing_thread = None
|
|
26
|
-
self.running = False
|
|
27
|
-
|
|
28
|
-
def add_memory(self, memory_item: MemoryItem):
|
|
29
|
-
"""Add memory to processing queue (non-blocking)"""
|
|
30
|
-
self.pending_queue.put(memory_item)
|
|
31
|
-
|
|
32
|
-
def start_background_processing(self):
|
|
33
|
-
"""Start background thread for memory processing"""
|
|
34
|
-
if self.processing_thread and self.processing_thread.is_alive():
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
self.running = True
|
|
38
|
-
self.processing_thread = threading.Thread(target=self._process_queue)
|
|
39
|
-
self.processing_thread.daemon = True
|
|
40
|
-
self.processing_thread.start()
|
|
41
|
-
|
|
42
|
-
def _process_queue(self):
|
|
43
|
-
"""Background processing of memory queue"""
|
|
44
|
-
while self.running:
|
|
45
|
-
try:
|
|
46
|
-
|
|
47
|
-
batch = []
|
|
48
|
-
try:
|
|
49
|
-
|
|
50
|
-
memory = self.pending_queue.get(timeout=1.0)
|
|
51
|
-
batch.append(memory)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
while len(batch) < 10:
|
|
55
|
-
try:
|
|
56
|
-
memory = self.pending_queue.get_nowait()
|
|
57
|
-
batch.append(memory)
|
|
58
|
-
except queue.Empty:
|
|
59
|
-
break
|
|
60
|
-
|
|
61
|
-
self._process_memory_batch(batch)
|
|
62
|
-
|
|
63
|
-
except queue.Empty:
|
|
64
|
-
continue
|
|
65
|
-
|
|
66
|
-
except Exception as e:
|
|
67
|
-
print(f"Error in memory processing: {e}")
|
|
68
|
-
time.sleep(1)
|
|
69
|
-
|
|
70
|
-
def _process_memory_batch(self, memories: List[MemoryItem]):
|
|
71
|
-
"""Process a batch of memories"""
|
|
72
|
-
for memory in memories:
|
|
73
|
-
|
|
74
|
-
memory_id = self.command_history.add_memory_to_database(
|
|
75
|
-
message_id=memory.message_id,
|
|
76
|
-
conversation_id=memory.conversation_id,
|
|
77
|
-
npc=memory.npc,
|
|
78
|
-
team=memory.team,
|
|
79
|
-
directory_path=memory.directory_path,
|
|
80
|
-
initial_memory=memory.content,
|
|
81
|
-
status="pending_approval",
|
|
82
|
-
model=memory.model,
|
|
83
|
-
provider=memory.provider
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
self.approval_results.put({
|
|
88
|
-
"memory_id": memory_id,
|
|
89
|
-
"content": memory.content,
|
|
90
|
-
"context": memory.context,
|
|
91
|
-
"npc": memory.npc
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
def get_approval_batch(self, max_items: int = 5) -> List[Dict]:
|
|
95
|
-
"""Get batch of memories ready for approval"""
|
|
96
|
-
batch = []
|
|
97
|
-
try:
|
|
98
|
-
while len(batch) < max_items:
|
|
99
|
-
item = self.approval_results.get_nowait()
|
|
100
|
-
batch.append(item)
|
|
101
|
-
except queue.Empty:
|
|
102
|
-
pass
|
|
103
|
-
return batch
|
|
104
|
-
|
|
105
|
-
def stop_processing(self):
|
|
106
|
-
"""Stop background processing"""
|
|
107
|
-
self.running = False
|
|
108
|
-
if self.processing_thread:
|
|
109
|
-
self.processing_thread.join(timeout=2.0)
|
|
110
|
-
|
|
111
|
-
def memory_approval_ui(memories: List[Dict]) -> List[Dict]:
|
|
112
|
-
"""Simple CLI interface for memory approval"""
|
|
113
|
-
if not memories:
|
|
114
|
-
return []
|
|
115
|
-
|
|
116
|
-
print(f"\n📝 {len(memories)} memories ready for approval:")
|
|
117
|
-
|
|
118
|
-
approvals = []
|
|
119
|
-
for i, memory in enumerate(memories, 1):
|
|
120
|
-
print(f"\n--- Memory {i}/{len(memories)} ---")
|
|
121
|
-
print(f"NPC: {memory['npc']}")
|
|
122
|
-
print(f"Content: {memory['content'][:200]}{'...' if len(memory['content']) > 200 else ''}")
|
|
123
|
-
|
|
124
|
-
while True:
|
|
125
|
-
choice = input("(a)pprove, (r)eject, (e)dit, (s)kip, (q)uit, (A)pprove all: ").strip().lower()
|
|
126
|
-
|
|
127
|
-
if choice == 'a':
|
|
128
|
-
approvals.append({"memory_id": memory['memory_id'], "decision": "human-approved"})
|
|
129
|
-
break
|
|
130
|
-
elif choice == 'r':
|
|
131
|
-
approvals.append({"memory_id": memory['memory_id'], "decision": "human-rejected"})
|
|
132
|
-
break
|
|
133
|
-
elif choice == 'e':
|
|
134
|
-
edited = input("Edit memory: ").strip()
|
|
135
|
-
if edited:
|
|
136
|
-
approvals.append({
|
|
137
|
-
"memory_id": memory['memory_id'],
|
|
138
|
-
"decision": "human-edited",
|
|
139
|
-
"final_memory": edited
|
|
140
|
-
})
|
|
141
|
-
break
|
|
142
|
-
elif choice == 's':
|
|
143
|
-
break
|
|
144
|
-
elif choice == 'q':
|
|
145
|
-
return approvals
|
|
146
|
-
elif choice == 'A':
|
|
147
|
-
|
|
148
|
-
for remaining_memory in memories[i-1:]:
|
|
149
|
-
approvals.append({
|
|
150
|
-
"memory_id": remaining_memory['memory_id'],
|
|
151
|
-
"decision": "human-approved"
|
|
152
|
-
})
|
|
153
|
-
return approvals
|
|
154
|
-
|
|
155
|
-
return approvals
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|