npcpy 1.3.18__py3-none-any.whl → 1.3.20__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/data/web.py +113 -8
- npcpy/llm_funcs.py +1 -3
- npcpy/memory/command_history.py +85 -9
- npcpy/memory/knowledge_graph.py +554 -33
- npcpy/memory/memory_processor.py +269 -53
- npcpy/npc_compiler.py +6 -0
- npcpy/serve.py +109 -2
- {npcpy-1.3.18.dist-info → npcpy-1.3.20.dist-info}/METADATA +3 -1
- {npcpy-1.3.18.dist-info → npcpy-1.3.20.dist-info}/RECORD +12 -12
- {npcpy-1.3.18.dist-info → npcpy-1.3.20.dist-info}/WHEEL +1 -1
- {npcpy-1.3.18.dist-info → npcpy-1.3.20.dist-info}/licenses/LICENSE +0 -0
- {npcpy-1.3.18.dist-info → npcpy-1.3.20.dist-info}/top_level.txt +0 -0
npcpy/memory/memory_processor.py
CHANGED
|
@@ -4,6 +4,13 @@ from datetime import datetime
|
|
|
4
4
|
import threading
|
|
5
5
|
import queue
|
|
6
6
|
import time
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from termcolor import colored
|
|
11
|
+
except ImportError:
|
|
12
|
+
def colored(text, color=None, on_color=None, attrs=None):
|
|
13
|
+
return text
|
|
7
14
|
|
|
8
15
|
@dataclass
|
|
9
16
|
class MemoryItem:
|
|
@@ -17,65 +24,274 @@ class MemoryItem:
|
|
|
17
24
|
model: str
|
|
18
25
|
provider: str
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
|
|
28
|
+
def _clear_line():
|
|
29
|
+
"""Clear current line in terminal."""
|
|
30
|
+
print('\r' + ' ' * 80 + '\r', end='')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _print_header(title: str, width: int = 60):
|
|
34
|
+
"""Print a styled header."""
|
|
35
|
+
print(colored("=" * width, "cyan"))
|
|
36
|
+
print(colored(f" {title}", "cyan", attrs=["bold"]))
|
|
37
|
+
print(colored("=" * width, "cyan"))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _print_memory_box(memory: Dict, index: int, total: int):
|
|
41
|
+
"""Print a memory in a nice box format."""
|
|
42
|
+
width = 70
|
|
43
|
+
|
|
44
|
+
# Header with progress
|
|
45
|
+
progress = f"[{index}/{total}]"
|
|
46
|
+
npc_info = f"NPC: {memory.get('npc', 'unknown')}"
|
|
47
|
+
header = f"{progress} {npc_info}"
|
|
48
|
+
print(colored("+" + "-" * (width - 2) + "+", "blue"))
|
|
49
|
+
print(colored(f"| {header:<{width-4}} |", "blue"))
|
|
50
|
+
print(colored("+" + "-" * (width - 2) + "+", "blue"))
|
|
51
|
+
|
|
52
|
+
# Content
|
|
53
|
+
content = memory.get('content', '')
|
|
54
|
+
# Wrap content to fit in box
|
|
55
|
+
lines = []
|
|
56
|
+
words = content.split()
|
|
57
|
+
current_line = ""
|
|
58
|
+
for word in words:
|
|
59
|
+
if len(current_line) + len(word) + 1 <= width - 6:
|
|
60
|
+
current_line += (" " if current_line else "") + word
|
|
61
|
+
else:
|
|
62
|
+
if current_line:
|
|
63
|
+
lines.append(current_line)
|
|
64
|
+
current_line = word
|
|
65
|
+
if current_line:
|
|
66
|
+
lines.append(current_line)
|
|
67
|
+
|
|
68
|
+
for line in lines[:6]: # Max 6 lines
|
|
69
|
+
print(colored(f"| {line:<{width-5}} |", "white"))
|
|
70
|
+
|
|
71
|
+
if len(lines) > 6:
|
|
72
|
+
print(colored(f"| {'...':<{width-5}} |", "grey"))
|
|
73
|
+
|
|
74
|
+
# Context if available
|
|
75
|
+
ctx = memory.get('context', '')
|
|
76
|
+
if ctx:
|
|
77
|
+
print(colored("+" + "-" * (width - 2) + "+", "blue"))
|
|
78
|
+
ctx_short = ctx[:width-8] + "..." if len(ctx) > width - 8 else ctx
|
|
79
|
+
print(colored(f"| {ctx_short:<{width-4}} |", "grey"))
|
|
80
|
+
|
|
81
|
+
print(colored("+" + "-" * (width - 2) + "+", "blue"))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _print_options():
|
|
85
|
+
"""Print available options."""
|
|
86
|
+
print()
|
|
87
|
+
options = [
|
|
88
|
+
(colored("a", "green", attrs=["bold"]), "approve"),
|
|
89
|
+
(colored("r", "red", attrs=["bold"]), "reject"),
|
|
90
|
+
(colored("e", "yellow", attrs=["bold"]), "edit"),
|
|
91
|
+
(colored("s", "grey"), "skip"),
|
|
92
|
+
(colored("A", "green"), "approve all"),
|
|
93
|
+
(colored("R", "red"), "reject all"),
|
|
94
|
+
(colored("D", "cyan"), "defer (review later)"),
|
|
95
|
+
]
|
|
96
|
+
print(" " + " | ".join([f"({k}) {v}" for k, v in options]))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _print_summary(stats: Dict):
|
|
100
|
+
"""Print approval summary."""
|
|
101
|
+
print()
|
|
102
|
+
_print_header("Memory Review Summary")
|
|
103
|
+
print(f" {colored('Approved:', 'green')} {stats.get('approved', 0)}")
|
|
104
|
+
print(f" {colored('Rejected:', 'red')} {stats.get('rejected', 0)}")
|
|
105
|
+
print(f" {colored('Edited:', 'yellow')} {stats.get('edited', 0)}")
|
|
106
|
+
print(f" {colored('Skipped:', 'grey')} {stats.get('skipped', 0)}")
|
|
107
|
+
print(f" {colored('Deferred:', 'cyan')} {stats.get('deferred', 0)}")
|
|
108
|
+
print()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def memory_approval_ui(memories: List[Dict], show_context: bool = True) -> List[Dict]:
|
|
112
|
+
"""
|
|
113
|
+
Enhanced memory approval UI with better formatting.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
memories: List of memory dicts with 'memory_id', 'content', 'npc', 'context'
|
|
117
|
+
show_context: Whether to show context info
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
List of approval dicts with 'memory_id', 'decision', optionally 'final_memory'
|
|
121
|
+
"""
|
|
21
122
|
if not memories:
|
|
22
123
|
return []
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
124
|
+
|
|
125
|
+
# Stats tracking
|
|
126
|
+
stats = {'approved': 0, 'rejected': 0, 'edited': 0, 'skipped': 0, 'deferred': 0}
|
|
26
127
|
approvals = []
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
128
|
+
|
|
129
|
+
print()
|
|
130
|
+
_print_header(f"Memory Review - {len(memories)} memories")
|
|
131
|
+
print()
|
|
132
|
+
|
|
133
|
+
i = 0
|
|
134
|
+
while i < len(memories):
|
|
135
|
+
memory = memories[i]
|
|
136
|
+
os.system('clear' if os.name == 'posix' else 'cls') if len(memories) > 3 else None
|
|
137
|
+
|
|
138
|
+
_print_memory_box(memory, i + 1, len(memories))
|
|
139
|
+
_print_options()
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
choice = input("\n Your choice: ").strip()
|
|
143
|
+
except (EOFError, KeyboardInterrupt):
|
|
144
|
+
print("\n Review cancelled.")
|
|
145
|
+
break
|
|
146
|
+
|
|
147
|
+
if choice == 'a':
|
|
148
|
+
approvals.append({
|
|
149
|
+
"memory_id": memory['memory_id'],
|
|
150
|
+
"decision": "human-approved"
|
|
151
|
+
})
|
|
152
|
+
stats['approved'] += 1
|
|
153
|
+
print(colored(" ✓ Approved", "green"))
|
|
154
|
+
i += 1
|
|
155
|
+
|
|
156
|
+
elif choice == 'r':
|
|
157
|
+
approvals.append({
|
|
158
|
+
"memory_id": memory['memory_id'],
|
|
159
|
+
"decision": "human-rejected"
|
|
160
|
+
})
|
|
161
|
+
stats['rejected'] += 1
|
|
162
|
+
print(colored(" ✗ Rejected", "red"))
|
|
163
|
+
i += 1
|
|
164
|
+
|
|
165
|
+
elif choice == 'e':
|
|
166
|
+
print(colored("\n Current:", "grey"), memory['content'][:100])
|
|
167
|
+
print(colored(" Enter new text (or empty to cancel):", "yellow"))
|
|
168
|
+
try:
|
|
169
|
+
edited = input(" > ").strip()
|
|
55
170
|
if edited:
|
|
56
171
|
approvals.append({
|
|
57
172
|
"memory_id": memory['memory_id'],
|
|
58
173
|
"decision": "human-edited",
|
|
59
174
|
"final_memory": edited
|
|
60
175
|
})
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
176
|
+
stats['edited'] += 1
|
|
177
|
+
print(colored(" ✎ Edited and approved", "yellow"))
|
|
178
|
+
i += 1
|
|
179
|
+
else:
|
|
180
|
+
print(colored(" Edit cancelled", "grey"))
|
|
181
|
+
except (EOFError, KeyboardInterrupt):
|
|
182
|
+
print(colored(" Edit cancelled", "grey"))
|
|
183
|
+
|
|
184
|
+
elif choice == 's':
|
|
185
|
+
stats['skipped'] += 1
|
|
186
|
+
print(colored(" ○ Skipped", "grey"))
|
|
187
|
+
i += 1
|
|
188
|
+
|
|
189
|
+
elif choice == 'A':
|
|
190
|
+
# Approve all remaining
|
|
191
|
+
for remaining in memories[i:]:
|
|
192
|
+
approvals.append({
|
|
193
|
+
"memory_id": remaining['memory_id'],
|
|
194
|
+
"decision": "human-approved"
|
|
195
|
+
})
|
|
196
|
+
stats['approved'] += 1
|
|
197
|
+
print(colored(f" ✓ Approved all {len(memories) - i} remaining", "green"))
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
elif choice == 'R':
|
|
201
|
+
# Reject all remaining
|
|
202
|
+
for remaining in memories[i:]:
|
|
203
|
+
approvals.append({
|
|
204
|
+
"memory_id": remaining['memory_id'],
|
|
205
|
+
"decision": "human-rejected"
|
|
206
|
+
})
|
|
207
|
+
stats['rejected'] += 1
|
|
208
|
+
print(colored(f" ✗ Rejected all {len(memories) - i} remaining", "red"))
|
|
209
|
+
break
|
|
210
|
+
|
|
211
|
+
elif choice == 'D':
|
|
212
|
+
# Defer - don't add to approvals, will remain pending
|
|
213
|
+
stats['deferred'] += len(memories) - i
|
|
214
|
+
print(colored(f" ⏸ Deferred {len(memories) - i} memories for later review", "cyan"))
|
|
215
|
+
break
|
|
216
|
+
|
|
217
|
+
elif choice == 'q':
|
|
218
|
+
print(colored(" Review ended", "grey"))
|
|
219
|
+
break
|
|
220
|
+
|
|
221
|
+
else:
|
|
222
|
+
print(colored(" Invalid choice. Use a/r/e/s/A/R/D", "red"))
|
|
223
|
+
|
|
224
|
+
time.sleep(0.2) # Brief pause for readability
|
|
225
|
+
|
|
226
|
+
_print_summary(stats)
|
|
227
|
+
return approvals
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def memory_batch_review_ui(
|
|
231
|
+
command_history,
|
|
232
|
+
npc_filter: str = None,
|
|
233
|
+
team_filter: str = None,
|
|
234
|
+
limit: int = 50
|
|
235
|
+
) -> Dict[str, int]:
|
|
236
|
+
"""
|
|
237
|
+
Review pending memories from the database in batch.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
command_history: CommandHistory instance
|
|
241
|
+
npc_filter: Optional NPC name filter
|
|
242
|
+
team_filter: Optional team name filter
|
|
243
|
+
limit: Max memories to review
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Dict with counts of approved/rejected/etc
|
|
247
|
+
"""
|
|
248
|
+
# Get pending memories
|
|
249
|
+
pending = command_history.get_pending_memories(limit=limit)
|
|
250
|
+
|
|
251
|
+
if not pending:
|
|
252
|
+
print(colored("No pending memories to review.", "grey"))
|
|
253
|
+
return {'approved': 0, 'rejected': 0, 'edited': 0, 'skipped': 0}
|
|
254
|
+
|
|
255
|
+
# Filter if specified
|
|
256
|
+
if npc_filter:
|
|
257
|
+
pending = [m for m in pending if m.get('npc') == npc_filter]
|
|
258
|
+
if team_filter:
|
|
259
|
+
pending = [m for m in pending if m.get('team') == team_filter]
|
|
260
|
+
|
|
261
|
+
if not pending:
|
|
262
|
+
print(colored("No memories match the filter criteria.", "grey"))
|
|
263
|
+
return {'approved': 0, 'rejected': 0, 'edited': 0, 'skipped': 0}
|
|
264
|
+
|
|
265
|
+
# Convert to format expected by approval UI
|
|
266
|
+
memories_for_ui = []
|
|
267
|
+
for m in pending:
|
|
268
|
+
memories_for_ui.append({
|
|
269
|
+
'memory_id': m.get('id'),
|
|
270
|
+
'content': m.get('initial_memory', ''),
|
|
271
|
+
'npc': m.get('npc', 'unknown'),
|
|
272
|
+
'context': f"Team: {m.get('team', 'unknown')} | Path: {m.get('directory_path', '')[:30]}"
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
# Run approval UI
|
|
276
|
+
approvals = memory_approval_ui(memories_for_ui)
|
|
277
|
+
|
|
278
|
+
# Apply approvals to database
|
|
279
|
+
stats = {'approved': 0, 'rejected': 0, 'edited': 0, 'skipped': 0}
|
|
280
|
+
|
|
281
|
+
for approval in approvals:
|
|
282
|
+
memory_id = approval['memory_id']
|
|
283
|
+
decision = approval['decision']
|
|
284
|
+
final_memory = approval.get('final_memory')
|
|
285
|
+
|
|
286
|
+
command_history.update_memory_status(memory_id, decision, final_memory)
|
|
287
|
+
|
|
288
|
+
if 'approved' in decision:
|
|
289
|
+
stats['approved'] += 1
|
|
290
|
+
elif 'rejected' in decision:
|
|
291
|
+
stats['rejected'] += 1
|
|
292
|
+
elif 'edited' in decision:
|
|
293
|
+
stats['edited'] += 1
|
|
294
|
+
|
|
295
|
+
stats['skipped'] = len(pending) - len(approvals)
|
|
296
|
+
|
|
297
|
+
return stats
|
npcpy/npc_compiler.py
CHANGED
|
@@ -1370,6 +1370,12 @@ class NPC:
|
|
|
1370
1370
|
# Fallback to direct name match if no base dir
|
|
1371
1371
|
matched_names = [jinx_spec] if jinx_spec in self.team.jinxs_dict else []
|
|
1372
1372
|
|
|
1373
|
+
if not matched_names:
|
|
1374
|
+
raise FileNotFoundError(
|
|
1375
|
+
f"NPC '{self.name}' references jinx '{jinx_spec}' but no matching jinx was found. "
|
|
1376
|
+
f"Available jinxs: {list(self.team.jinxs_dict.keys())[:20]}..."
|
|
1377
|
+
)
|
|
1378
|
+
|
|
1373
1379
|
for jinx_name in matched_names:
|
|
1374
1380
|
if jinx_name in self.team.jinxs_dict:
|
|
1375
1381
|
self.jinxs_dict[jinx_name] = self.team.jinxs_dict[jinx_name]
|
npcpy/serve.py
CHANGED
|
@@ -58,7 +58,7 @@ import base64
|
|
|
58
58
|
import shutil
|
|
59
59
|
import uuid
|
|
60
60
|
|
|
61
|
-
from npcpy.llm_funcs import gen_image, breathe
|
|
61
|
+
from npcpy.llm_funcs import gen_image, gen_video, breathe
|
|
62
62
|
|
|
63
63
|
from sqlalchemy import create_engine, text
|
|
64
64
|
from sqlalchemy.orm import sessionmaker
|
|
@@ -4333,6 +4333,89 @@ def get_image_models_api():
|
|
|
4333
4333
|
return jsonify({"models": [], "error": str(e)}), 500
|
|
4334
4334
|
|
|
4335
4335
|
|
|
4336
|
+
@app.route("/api/generate_video", methods=["POST"])
|
|
4337
|
+
def generate_video_api():
|
|
4338
|
+
"""
|
|
4339
|
+
API endpoint for video generation.
|
|
4340
|
+
"""
|
|
4341
|
+
try:
|
|
4342
|
+
data = request.get_json()
|
|
4343
|
+
prompt = data.get("prompt", "")
|
|
4344
|
+
model = data.get("model", "veo-3.1-generate-preview")
|
|
4345
|
+
provider = data.get("provider", "gemini")
|
|
4346
|
+
duration = data.get("duration", 5)
|
|
4347
|
+
output_dir = data.get("output_dir") # Optional user-specified path
|
|
4348
|
+
negative_prompt = data.get("negative_prompt", "")
|
|
4349
|
+
reference_image = data.get("reference_image") # Optional base64 image
|
|
4350
|
+
|
|
4351
|
+
if not prompt:
|
|
4352
|
+
return jsonify({"error": "Prompt is required"}), 400
|
|
4353
|
+
|
|
4354
|
+
# Create output directory - use user-specified path or default to ~/.npcsh/videos
|
|
4355
|
+
if output_dir:
|
|
4356
|
+
save_dir = os.path.expanduser(output_dir)
|
|
4357
|
+
else:
|
|
4358
|
+
save_dir = os.path.expanduser("~/.npcsh/videos")
|
|
4359
|
+
os.makedirs(save_dir, exist_ok=True)
|
|
4360
|
+
|
|
4361
|
+
# Generate unique filename
|
|
4362
|
+
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
4363
|
+
output_filename = f"video_{timestamp}.mp4"
|
|
4364
|
+
output_path = os.path.join(save_dir, output_filename)
|
|
4365
|
+
|
|
4366
|
+
# Calculate num_frames based on duration (assuming ~25fps for diffusers)
|
|
4367
|
+
num_frames = int(duration * 25) if provider == "diffusers" else 25
|
|
4368
|
+
|
|
4369
|
+
print(f"Generating video with model={model}, provider={provider}, duration={duration}s")
|
|
4370
|
+
|
|
4371
|
+
result = gen_video(
|
|
4372
|
+
prompt=prompt,
|
|
4373
|
+
model=model,
|
|
4374
|
+
provider=provider,
|
|
4375
|
+
output_path=output_path,
|
|
4376
|
+
num_frames=num_frames,
|
|
4377
|
+
negative_prompt=negative_prompt,
|
|
4378
|
+
)
|
|
4379
|
+
|
|
4380
|
+
if result and "output" in result:
|
|
4381
|
+
# Read the generated video file and encode to base64
|
|
4382
|
+
video_path = output_path
|
|
4383
|
+
if os.path.exists(video_path):
|
|
4384
|
+
with open(video_path, "rb") as f:
|
|
4385
|
+
video_data = f.read()
|
|
4386
|
+
video_base64 = base64.b64encode(video_data).decode("utf-8")
|
|
4387
|
+
|
|
4388
|
+
return jsonify({
|
|
4389
|
+
"success": True,
|
|
4390
|
+
"video_path": video_path,
|
|
4391
|
+
"video_base64": f"data:video/mp4;base64,{video_base64}",
|
|
4392
|
+
"message": result.get("output", "Video generated successfully")
|
|
4393
|
+
})
|
|
4394
|
+
else:
|
|
4395
|
+
return jsonify({"error": "Video file was not created"}), 500
|
|
4396
|
+
else:
|
|
4397
|
+
return jsonify({"error": result.get("output", "Video generation failed")}), 500
|
|
4398
|
+
|
|
4399
|
+
except Exception as e:
|
|
4400
|
+
print(f"Error generating video: {e}")
|
|
4401
|
+
traceback.print_exc()
|
|
4402
|
+
return jsonify({"error": str(e)}), 500
|
|
4403
|
+
|
|
4404
|
+
|
|
4405
|
+
@app.route("/api/video_models", methods=["GET"])
|
|
4406
|
+
def get_video_models_api():
|
|
4407
|
+
"""
|
|
4408
|
+
API endpoint to retrieve available video generation models.
|
|
4409
|
+
"""
|
|
4410
|
+
video_models = [
|
|
4411
|
+
# Google Veo via Gemini API (requires GEMINI_API_KEY)
|
|
4412
|
+
{"value": "veo-3.1-generate-preview", "display_name": "Veo 3.1 | gemini", "provider": "gemini", "max_duration": 8},
|
|
4413
|
+
{"value": "veo-3.1-fast-generate-preview", "display_name": "Veo 3.1 Fast | gemini", "provider": "gemini", "max_duration": 8},
|
|
4414
|
+
{"value": "veo-2.0-generate-001", "display_name": "Veo 2 | gemini", "provider": "gemini", "max_duration": 8},
|
|
4415
|
+
# Diffusers - damo-vilab/text-to-video-ms-1.7b (local)
|
|
4416
|
+
{"value": "damo-vilab/text-to-video-ms-1.7b", "display_name": "ModelScope 1.7B (Local) | diffusers", "provider": "diffusers", "max_duration": 4},
|
|
4417
|
+
]
|
|
4418
|
+
return jsonify({"models": video_models, "error": None})
|
|
4336
4419
|
|
|
4337
4420
|
|
|
4338
4421
|
|
|
@@ -4561,6 +4644,17 @@ def stream():
|
|
|
4561
4644
|
frontend_assistant_message_id = data.get("assistantMessageId", None)
|
|
4562
4645
|
# For sub-branches: the parent of the user message (points to an assistant message)
|
|
4563
4646
|
user_parent_message_id = data.get("userParentMessageId", None)
|
|
4647
|
+
# LLM generation parameters - build params dict if any are provided
|
|
4648
|
+
params = {}
|
|
4649
|
+
if data.get("temperature") is not None:
|
|
4650
|
+
params["temperature"] = data.get("temperature")
|
|
4651
|
+
if data.get("top_p") is not None:
|
|
4652
|
+
params["top_p"] = data.get("top_p")
|
|
4653
|
+
if data.get("top_k") is not None:
|
|
4654
|
+
params["top_k"] = data.get("top_k")
|
|
4655
|
+
if data.get("max_tokens") is not None:
|
|
4656
|
+
params["max_tokens"] = data.get("max_tokens")
|
|
4657
|
+
params = params if params else None
|
|
4564
4658
|
|
|
4565
4659
|
if current_path:
|
|
4566
4660
|
loaded_vars = load_project_env(current_path)
|
|
@@ -4684,6 +4778,16 @@ def stream():
|
|
|
4684
4778
|
if os.path.exists(file_path):
|
|
4685
4779
|
with open(file_path, "rb") as f:
|
|
4686
4780
|
file_content_bytes = f.read()
|
|
4781
|
+
else:
|
|
4782
|
+
print(f"Warning: Attachment file does not exist: {file_path}")
|
|
4783
|
+
# Try data fallback if path doesn't exist
|
|
4784
|
+
if "data" in attachment and attachment["data"]:
|
|
4785
|
+
file_content_bytes = base64.b64decode(attachment["data"])
|
|
4786
|
+
import tempfile
|
|
4787
|
+
temp_dir = tempfile.mkdtemp()
|
|
4788
|
+
file_path = os.path.join(temp_dir, file_name)
|
|
4789
|
+
with open(file_path, "wb") as f:
|
|
4790
|
+
f.write(file_content_bytes)
|
|
4687
4791
|
|
|
4688
4792
|
# Fall back to base64 data if no path
|
|
4689
4793
|
elif "data" in attachment and attachment["data"]:
|
|
@@ -4695,7 +4799,8 @@ def stream():
|
|
|
4695
4799
|
with open(file_path, "wb") as f:
|
|
4696
4800
|
f.write(file_content_bytes)
|
|
4697
4801
|
|
|
4698
|
-
if not file_path:
|
|
4802
|
+
if not file_path or file_content_bytes is None:
|
|
4803
|
+
print(f"Warning: Skipping attachment {file_name} - no valid path or data")
|
|
4699
4804
|
continue
|
|
4700
4805
|
|
|
4701
4806
|
attachment_paths_for_llm.append(file_path)
|
|
@@ -5092,6 +5197,7 @@ def stream():
|
|
|
5092
5197
|
attachments=attachments_for_db,
|
|
5093
5198
|
message_id=message_id,
|
|
5094
5199
|
parent_message_id=user_parent_message_id, # For sub-branches: points to assistant message
|
|
5200
|
+
gen_params=params,
|
|
5095
5201
|
)
|
|
5096
5202
|
|
|
5097
5203
|
|
|
@@ -5389,6 +5495,7 @@ def stream():
|
|
|
5389
5495
|
tool_calls=accumulated_tool_calls if accumulated_tool_calls else None,
|
|
5390
5496
|
tool_results=tool_results_for_db if tool_results_for_db else None,
|
|
5391
5497
|
parent_message_id=parent_message_id,
|
|
5498
|
+
gen_params=params,
|
|
5392
5499
|
)
|
|
5393
5500
|
|
|
5394
5501
|
# Start background tasks for memory extraction and context compression
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: npcpy
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.20
|
|
4
4
|
Summary: npcpy is the premier open-source library for integrating LLMs and Agents into python systems.
|
|
5
5
|
Home-page: https://github.com/NPC-Worldwide/npcpy
|
|
6
6
|
Author: Christopher Agostino
|
|
@@ -45,6 +45,7 @@ Requires-Dist: mcp
|
|
|
45
45
|
Provides-Extra: lite
|
|
46
46
|
Requires-Dist: anthropic; extra == "lite"
|
|
47
47
|
Requires-Dist: openai; extra == "lite"
|
|
48
|
+
Requires-Dist: ollama; extra == "lite"
|
|
48
49
|
Requires-Dist: google-generativeai; extra == "lite"
|
|
49
50
|
Requires-Dist: google-genai; extra == "lite"
|
|
50
51
|
Provides-Extra: local
|
|
@@ -66,6 +67,7 @@ Requires-Dist: pyttsx3; extra == "yap"
|
|
|
66
67
|
Provides-Extra: all
|
|
67
68
|
Requires-Dist: anthropic; extra == "all"
|
|
68
69
|
Requires-Dist: openai; extra == "all"
|
|
70
|
+
Requires-Dist: ollama; extra == "all"
|
|
69
71
|
Requires-Dist: google-generativeai; extra == "all"
|
|
70
72
|
Requires-Dist: google-genai; extra == "all"
|
|
71
73
|
Requires-Dist: sentence_transformers; extra == "all"
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
npcpy/__init__.py,sha256=uJcJGjR1mWvE69GySNAufkgiRwJA28zdObDBWaxp0tY,505
|
|
2
2
|
npcpy/build_funcs.py,sha256=vOz6pjV0zS-kYKo0ux-pn9AcppVaR8KIDi2ldOxb3RQ,7479
|
|
3
|
-
npcpy/llm_funcs.py,sha256=
|
|
3
|
+
npcpy/llm_funcs.py,sha256=ghi0y0qlngwc5BZLa-aiFpa0GT7qLe56UboI2dLu75U,75055
|
|
4
4
|
npcpy/main.py,sha256=RWoRIj6VQLxKdOKvdVyaq2kwG35oRpeXPvp1CAAoG-w,81
|
|
5
5
|
npcpy/ml_funcs.py,sha256=smgeOLnjxGWjmDngE-bcA2ozXX_IzY_7_pS9h2iocEg,24249
|
|
6
6
|
npcpy/npc_array.py,sha256=5qjaA9KjmJ_Zk_VxLrCyVrj73aDXpm3iJf0ngq1yIJk,45721
|
|
7
|
-
npcpy/npc_compiler.py,sha256=
|
|
7
|
+
npcpy/npc_compiler.py,sha256=s_67cyvoao0UwUa4N-jrceWDadvBB9TWYpxcjNPfO18,121941
|
|
8
8
|
npcpy/npc_sysenv.py,sha256=1E2zwMj7aPrtRJuJSowGkvNApi07Vue3FhXsipi1XDs,45251
|
|
9
9
|
npcpy/npcs.py,sha256=eExuVsbTfrRobTRRptRpDm46jCLWUgbvy4_U7IUQo-c,744
|
|
10
|
-
npcpy/serve.py,sha256=
|
|
10
|
+
npcpy/serve.py,sha256=P71oa4eVvG0SePkOoeeQ44LkRAfnlgElxZi9GUVG7qg,280598
|
|
11
11
|
npcpy/tools.py,sha256=A5_oVmZkzGnI3BI-NmneuxeXQq-r29PbpAZP4nV4jrc,5303
|
|
12
12
|
npcpy/data/__init__.py,sha256=1tcoChR-Hjn905JDLqaW9ElRmcISCTJdE7BGXPlym2Q,642
|
|
13
13
|
npcpy/data/audio.py,sha256=o4auV8DQrAmZ4y84U3SofiwEuq5-ZBjGEZipQ9zPpGQ,22816
|
|
@@ -16,7 +16,7 @@ npcpy/data/image.py,sha256=UQcioNPDd5HYMLL_KStf45SuiIPXDcUY-dEFHwSWUeE,6564
|
|
|
16
16
|
npcpy/data/load.py,sha256=rVe1xSHerIpo6MDaY5eIeqRSm0gssX5sHukNsUNVwJw,9228
|
|
17
17
|
npcpy/data/text.py,sha256=jP0a1qZZaSJdK-LdZTn2Jjdxqmkd3efxDLEoxflJQeY,5010
|
|
18
18
|
npcpy/data/video.py,sha256=H-V3mTu_ktD9u-QhYeo4aW3u9z0AtoAdRZmvRPEpE98,2887
|
|
19
|
-
npcpy/data/web.py,sha256=
|
|
19
|
+
npcpy/data/web.py,sha256=qmioqCxxWddRnNm2ke39_SWXXNSqRZ1Nx86_-1JGq_A,9139
|
|
20
20
|
npcpy/ft/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
npcpy/ft/diff.py,sha256=0ScRR4AxXtVX2bgZ-Jr_dSwv3LAlU1JXDUq4F4n1Ea4,12839
|
|
22
22
|
npcpy/ft/ge.py,sha256=0VzIiXq2wCzGcK1x0Wd-myJ3xRf-FNaPg0GkHEZegUM,3552
|
|
@@ -34,10 +34,10 @@ npcpy/gen/response.py,sha256=EYsIOvNOmn6dBs-4j3SyZNMvDf5N9lW-QxMbpjnF7Kw,57081
|
|
|
34
34
|
npcpy/gen/video_gen.py,sha256=RFi3Zcq_Hn3HIcfoF3mijQ6G7RYFZaM_9pjPTh-8E64,3239
|
|
35
35
|
npcpy/gen/world_gen.py,sha256=_8ytE7E3QVQ5qiX8DmOby-xd0d9zV20rRI6Wkpf-qcY,18922
|
|
36
36
|
npcpy/memory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
npcpy/memory/command_history.py,sha256=
|
|
37
|
+
npcpy/memory/command_history.py,sha256=_2ygoMO4EwX8H2f5G2Q62hrgqMwL7weOI8CTaFPn-I0,66258
|
|
38
38
|
npcpy/memory/kg_vis.py,sha256=TrQQCRh_E7Pyr-GPAHLSsayubAfGyf4HOEFrPB6W86Q,31280
|
|
39
|
-
npcpy/memory/knowledge_graph.py,sha256=
|
|
40
|
-
npcpy/memory/memory_processor.py,sha256=
|
|
39
|
+
npcpy/memory/knowledge_graph.py,sha256=RHznD_Dt5Ka_eD015QuXnh3aG61c0Tf4oRIfY_JzPDQ,67554
|
|
40
|
+
npcpy/memory/memory_processor.py,sha256=T6VAeLYNfwP1jjDDY4x6Me_l5xcVShIcU95I1ye5nEM,9453
|
|
41
41
|
npcpy/memory/search.py,sha256=glN6WYzaixcoDphTEHAXSMX3vKZGjR12Jx9YVL_gYfE,18433
|
|
42
42
|
npcpy/mix/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
43
|
npcpy/mix/debate.py,sha256=lQXxC7nl6Rwyf7HIYrsVQILMUmYYx55Tjt2pkTg56qY,9019
|
|
@@ -53,8 +53,8 @@ npcpy/work/browser.py,sha256=p2PeaoZdAXipFuAgKCCB3aXXLE_p3yIRqC87KlZKZWc,679
|
|
|
53
53
|
npcpy/work/desktop.py,sha256=F3I8mUtJp6LAkXodsh8hGZIncoads6c_2Utty-0EdDA,2986
|
|
54
54
|
npcpy/work/plan.py,sha256=QyUwg8vElWiHuoS-xK4jXTxxHvkMD3VkaCEsCmrEPQk,8300
|
|
55
55
|
npcpy/work/trigger.py,sha256=P1Y8u1wQRsS2WACims_2IdkBEar-iBQix-2TDWoW0OM,9948
|
|
56
|
-
npcpy-1.3.
|
|
57
|
-
npcpy-1.3.
|
|
58
|
-
npcpy-1.3.
|
|
59
|
-
npcpy-1.3.
|
|
60
|
-
npcpy-1.3.
|
|
56
|
+
npcpy-1.3.20.dist-info/licenses/LICENSE,sha256=j0YPvce7Ng9e32zYOu0EmXjXeJ0Nwawd0RA3uSGGH4E,1070
|
|
57
|
+
npcpy-1.3.20.dist-info/METADATA,sha256=quc7U-oA3yF-Kp_lb9N-EReQCfcfO8YBBqp2pqrbnTU,37947
|
|
58
|
+
npcpy-1.3.20.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
59
|
+
npcpy-1.3.20.dist-info/top_level.txt,sha256=g1pbSvrOOncB74Bg5-J0Olg4V0A5VzDw-Xz5YObq8BU,6
|
|
60
|
+
npcpy-1.3.20.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|