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
npcpy/modes/plonk.py
DELETED
|
@@ -1,409 +0,0 @@
|
|
|
1
|
-
from npcpy.data.image import capture_screenshot
|
|
2
|
-
import time
|
|
3
|
-
import platform
|
|
4
|
-
from npcpy.llm_funcs import get_llm_response
|
|
5
|
-
from npcpy.work.desktop import perform_action, action_space
|
|
6
|
-
from PIL import Image, ImageDraw, ImageFont
|
|
7
|
-
|
|
8
|
-
def get_system_examples():
|
|
9
|
-
system = platform.system()
|
|
10
|
-
if system == "Windows":
|
|
11
|
-
return "Examples: start firefox, notepad, calc, explorer"
|
|
12
|
-
elif system == "Darwin":
|
|
13
|
-
return "Examples: open -a Firefox, open -a TextEdit, open -a Calculator"
|
|
14
|
-
else:
|
|
15
|
-
return "Examples: firefox &, gedit &, gnome-calculator &"
|
|
16
|
-
|
|
17
|
-
def execute_plonk_command(request, action_space, model, provider, npc=None, max_iterations=10, debug=False):
|
|
18
|
-
synthesized_summary = []
|
|
19
|
-
|
|
20
|
-
"""Synthesizes information gathered during the computer use run and logs key data points for
|
|
21
|
-
analysis. This function can be extended to store or report the synthesized knowledge as required.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
system = platform.system()
|
|
25
|
-
system_examples = get_system_examples()
|
|
26
|
-
|
|
27
|
-
messages = []
|
|
28
|
-
last_action_feedback = "None"
|
|
29
|
-
last_click_coords = None
|
|
30
|
-
|
|
31
|
-
iteration_count = 0
|
|
32
|
-
while iteration_count < max_iterations:
|
|
33
|
-
# Gathering summary of actions performed this iteration
|
|
34
|
-
synthesized_info = {
|
|
35
|
-
'iteration': iteration_count + 1,
|
|
36
|
-
'last_action_feedback': last_action_feedback,
|
|
37
|
-
'last_click_coords': last_click_coords
|
|
38
|
-
}
|
|
39
|
-
synthesized_summary.append(synthesized_info)
|
|
40
|
-
|
|
41
|
-
if debug:
|
|
42
|
-
print(f"Synthesized info at iteration {iteration_count + 1}: {synthesized_info}")
|
|
43
|
-
|
|
44
|
-
if debug:
|
|
45
|
-
print(f"Iteration {iteration_count + 1}/{max_iterations}")
|
|
46
|
-
|
|
47
|
-
# YOUR PROMPT, UNTOUCHED
|
|
48
|
-
prompt_template = f"""
|
|
49
|
-
Goal: {request}
|
|
50
|
-
Feedback from last action: {last_action_feedback}
|
|
51
|
-
|
|
52
|
-
Your task is to control the computer to achieve the goal.
|
|
53
|
-
|
|
54
|
-
THOUGHT PROCESS:
|
|
55
|
-
1. Analyze the screen. Is the application I need (e.g., a web browser) already open?
|
|
56
|
-
2. If YES, `click` it. If NO, use `bash` to launch it. Use the examples: {system_examples}.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
CRITICAL COMPLETION RULE:
|
|
60
|
-
Once the goal is visually complete on the screen, your ONLY next action is to use the 'quit' action.
|
|
61
|
-
|
|
62
|
-
Your response MUST be a JSON object with an "actions" key.
|
|
63
|
-
All clicking actions should use percentage coordinates relative
|
|
64
|
-
to the screen size, as we will
|
|
65
|
-
manually translate them to the proper screen size.
|
|
66
|
-
your x and y values for clicks must ALWAYS be between 0 and 100.
|
|
67
|
-
The x and y are (0,0) at the TOP LEFT CORNER OF THE SCREEN.
|
|
68
|
-
The bottom right corner of the screen is (100,100).
|
|
69
|
-
the bottom left corner is (0,100) and the top right corner is (100,0).
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
EXAMPLE 1: Task "Create and save a file named 'memo.txt' with the text 'Meeting at 3pm'"
|
|
76
|
-
{{
|
|
77
|
-
"actions": [
|
|
78
|
-
{{ "type": "bash", "command": "gedit &" }},
|
|
79
|
-
{{ "type": "wait", "duration": 2 }},
|
|
80
|
-
{{'type':'click', 'x': 10, 'y': 30}},
|
|
81
|
-
{{ "type": "type", "text": "Meeting at 3pm" }},
|
|
82
|
-
{{ "type": "hotkey", "keys": ["ctrl", "s"] }},
|
|
83
|
-
{{ "type": "wait", "duration": 1 }},
|
|
84
|
-
{{ "type": "type", "text": "memo.txt" }},
|
|
85
|
-
{{ "type": "key", "keys": ["enter"] }},
|
|
86
|
-
]
|
|
87
|
-
}}
|
|
88
|
-
---
|
|
89
|
-
EXAMPLE 2: Task "Search for news about space exploration"
|
|
90
|
-
{{
|
|
91
|
-
"actions": [
|
|
92
|
-
{{ "type": "bash", "command": "firefox &" }},
|
|
93
|
-
{{ "type": "wait", "duration": 3 }},
|
|
94
|
-
{{ "type": "type", "text": "news about space exploration" }},
|
|
95
|
-
{{ "type": "key", "keys": ["enter"] }},
|
|
96
|
-
]
|
|
97
|
-
}}
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
Once a task has been verified and completed, your action list should only be
|
|
102
|
-
{{
|
|
103
|
-
"actions": [
|
|
104
|
-
{{ "type": "quit" }}
|
|
105
|
-
]
|
|
106
|
-
}}
|
|
107
|
-
"""
|
|
108
|
-
|
|
109
|
-
screenshot_path = capture_screenshot(npc=npc, full=True).get('file_path')
|
|
110
|
-
if not screenshot_path:
|
|
111
|
-
time.sleep(2)
|
|
112
|
-
continue
|
|
113
|
-
|
|
114
|
-
image_to_send_path = screenshot_path
|
|
115
|
-
if last_click_coords:
|
|
116
|
-
try:
|
|
117
|
-
img = Image.open(screenshot_path)
|
|
118
|
-
draw = ImageDraw.Draw(img)
|
|
119
|
-
width, height = img.size
|
|
120
|
-
x_pixel = int(last_click_coords['x'] * width / 100)
|
|
121
|
-
y_pixel = int(last_click_coords['y'] * height / 100)
|
|
122
|
-
|
|
123
|
-
try:
|
|
124
|
-
font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=48)
|
|
125
|
-
except IOError:
|
|
126
|
-
font = ImageFont.load_default()
|
|
127
|
-
|
|
128
|
-
draw.text((x_pixel - 8, y_pixel - 12),
|
|
129
|
-
f"+{last_click_coords['x'],last_click_coords['y']}",
|
|
130
|
-
fill="red",
|
|
131
|
-
font=font)
|
|
132
|
-
|
|
133
|
-
marked_image_path = "/tmp/marked_screenshot.png"
|
|
134
|
-
img.save(marked_image_path)
|
|
135
|
-
image_to_send_path = marked_image_path
|
|
136
|
-
print(f"Drew marker at ({x_pixel}, {y_pixel}) on new screenshot.")
|
|
137
|
-
except Exception as e:
|
|
138
|
-
print(f"Failed to draw marker on image: {e}")
|
|
139
|
-
|
|
140
|
-
response = get_llm_response(
|
|
141
|
-
prompt=prompt_template,
|
|
142
|
-
model=model,
|
|
143
|
-
provider=provider,
|
|
144
|
-
npc=npc,
|
|
145
|
-
images=[image_to_send_path],
|
|
146
|
-
messages=messages,
|
|
147
|
-
format="json",
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
if "messages" in response:
|
|
151
|
-
messages = response["messages"]
|
|
152
|
-
|
|
153
|
-
response_data = response.get('response')
|
|
154
|
-
|
|
155
|
-
if not isinstance(response_data, dict) or "actions" not in response_data:
|
|
156
|
-
last_action_feedback = f"Invalid JSON response from model: {response_data}"
|
|
157
|
-
continue
|
|
158
|
-
|
|
159
|
-
actions_list = response_data.get("actions", [])
|
|
160
|
-
|
|
161
|
-
if not isinstance(actions_list, list):
|
|
162
|
-
last_action_feedback = "Model did not return a list in the 'actions' key."
|
|
163
|
-
continue
|
|
164
|
-
|
|
165
|
-
# Reset last click before processing new actions
|
|
166
|
-
last_click_coords = None
|
|
167
|
-
for action in actions_list:
|
|
168
|
-
if debug:
|
|
169
|
-
print(f"Executing action: {action}")
|
|
170
|
-
if action.get("type") == "quit":
|
|
171
|
-
print("Task complete: Model returned 'quit' action.")
|
|
172
|
-
return "SUCCESS"
|
|
173
|
-
|
|
174
|
-
result = perform_action(action)
|
|
175
|
-
last_action_feedback = result.get("message") or result.get("output")
|
|
176
|
-
|
|
177
|
-
if action.get("type") == "click":
|
|
178
|
-
last_click_coords = {"x": action.get("x"), "y": action.get("y")}
|
|
179
|
-
|
|
180
|
-
if result.get("status") == "error":
|
|
181
|
-
print(f"Action failed, providing feedback to model: {last_action_feedback}")
|
|
182
|
-
break
|
|
183
|
-
time.sleep(1)
|
|
184
|
-
|
|
185
|
-
if not actions_list:
|
|
186
|
-
last_action_feedback = "No actions were returned. The task is likely not complete. Re-evaluating."
|
|
187
|
-
print(last_action_feedback)
|
|
188
|
-
|
|
189
|
-
iteration_count += 1
|
|
190
|
-
|
|
191
|
-
return None
|
|
192
|
-
|
|
193
|
-
def synthesize_and_display_summary(synthesized_summary, debug=False):
|
|
194
|
-
"""Synthesizes information gathered during the computer use run and logs key data points."""
|
|
195
|
-
if not synthesized_summary:
|
|
196
|
-
print("No synthesized info to display.")
|
|
197
|
-
return
|
|
198
|
-
|
|
199
|
-
print("\nSynthesized Summary of Computer Use Run:")
|
|
200
|
-
for info in synthesized_summary:
|
|
201
|
-
print(f"Iteration {info['iteration']}:\n"
|
|
202
|
-
f" Last Action Feedback: {info['last_action_feedback']}\n"
|
|
203
|
-
f" Last Click Coordinates: {info['last_click_coords']}")
|
|
204
|
-
print("End of synthesized summary.\n")
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
def repl_loop():
|
|
209
|
-
print("Assistant REPL - Type your plonk command or 'exit' to quit.")
|
|
210
|
-
while True:
|
|
211
|
-
user_input = input("Enter your command: ").strip()
|
|
212
|
-
if user_input.lower() == 'exit':
|
|
213
|
-
print("Exiting REPL. Goodbye!")
|
|
214
|
-
break
|
|
215
|
-
if not user_input:
|
|
216
|
-
continue
|
|
217
|
-
|
|
218
|
-
# Run the plonk command and get synthesized summary
|
|
219
|
-
synthesized_summary = execute_plonk_command(
|
|
220
|
-
request=user_input,
|
|
221
|
-
action_space=action_space,
|
|
222
|
-
model="gpt-4o-mini",
|
|
223
|
-
provider="openai",
|
|
224
|
-
max_iterations=8,
|
|
225
|
-
debug=True
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
if synthesized_summary and isinstance(synthesized_summary, list):
|
|
229
|
-
print("Command executed with synthesized summary.")
|
|
230
|
-
synthesize_and_display_summary(synthesized_summary)
|
|
231
|
-
else:
|
|
232
|
-
print("Command did not complete within iteration limit or returned no summary.")
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def execute_plonk_command(request, action_space, model, provider, npc=None, max_iterations=10, debug=False):
|
|
236
|
-
"""Synthesizes information gathered during the computer use run and logs key data points for
|
|
237
|
-
analysis. This function can be extended to store or report the synthesized knowledge as required.
|
|
238
|
-
"""
|
|
239
|
-
|
|
240
|
-
system = platform.system()
|
|
241
|
-
system_examples = get_system_examples()
|
|
242
|
-
|
|
243
|
-
messages = []
|
|
244
|
-
last_action_feedback = "None"
|
|
245
|
-
last_click_coords = None
|
|
246
|
-
|
|
247
|
-
iteration_count = 0
|
|
248
|
-
|
|
249
|
-
synthesized_summary = []
|
|
250
|
-
|
|
251
|
-
while iteration_count < max_iterations:
|
|
252
|
-
synthesized_info = {
|
|
253
|
-
'iteration': iteration_count + 1,
|
|
254
|
-
'last_action_feedback': last_action_feedback,
|
|
255
|
-
'last_click_coords': last_click_coords
|
|
256
|
-
}
|
|
257
|
-
synthesized_summary.append(synthesized_info)
|
|
258
|
-
|
|
259
|
-
if debug:
|
|
260
|
-
print(f"Synthesized info at iteration {iteration_count + 1}: {synthesized_info}")
|
|
261
|
-
|
|
262
|
-
if debug:
|
|
263
|
-
print(f"Iteration {iteration_count + 1}/{max_iterations}")
|
|
264
|
-
|
|
265
|
-
prompt_template = f"""
|
|
266
|
-
Goal: {request}
|
|
267
|
-
Feedback from last action: {last_action_feedback}
|
|
268
|
-
|
|
269
|
-
Your task is to control the computer to achieve the goal.
|
|
270
|
-
|
|
271
|
-
THOUGHT PROCESS:
|
|
272
|
-
1. Analyze the screen. Is the application I need (e.g., a web browser) already open?
|
|
273
|
-
2. If YES, `click` it. If NO, use `bash` to launch it. Use the examples: {system_examples}.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
CRITICAL COMPLETION RULE:
|
|
277
|
-
Once the goal is visually complete on the screen, your ONLY next action is to use the 'quit' action.
|
|
278
|
-
|
|
279
|
-
Your response MUST be a JSON object with an "actions" key.
|
|
280
|
-
All clicking actions should use percentage coordinates relative
|
|
281
|
-
to the screen size, as we will
|
|
282
|
-
manually translate them to the proper screen size.
|
|
283
|
-
your x and y values for clicks must ALWAYS be between 0 and 100.
|
|
284
|
-
The x and y are (0,0) at the TOP LEFT CORNER OF THE SCREEN.
|
|
285
|
-
The bottom right corner of the screen is (100,100).
|
|
286
|
-
the bottom left corner is (0,100) and the top right corner is (100,0).
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
---
|
|
291
|
-
EXAMPLE 1: Task "Create and save a file named 'memo.txt' with the text 'Meeting at 3pm'"
|
|
292
|
-
{{
|
|
293
|
-
"actions": [
|
|
294
|
-
{{ "type": "bash", "command": "gedit &" }},
|
|
295
|
-
{{ "type": "wait", "duration": 2 }},
|
|
296
|
-
{{'type':'click', 'x': 10, 'y': 30}},
|
|
297
|
-
{{ "type": "type", "text": "Meeting at 3pm" }},
|
|
298
|
-
{{ "type": "hotkey", "keys": ["ctrl", "s"] }},
|
|
299
|
-
{{ "type": "wait", "duration": 1 }},
|
|
300
|
-
{{ "type": "type", "text": "memo.txt" }},
|
|
301
|
-
{{ "type": "key", "keys": ["enter"] }},
|
|
302
|
-
]
|
|
303
|
-
}}
|
|
304
|
-
---
|
|
305
|
-
EXAMPLE 2: Task "Search for news about space exploration"
|
|
306
|
-
{{
|
|
307
|
-
"actions": [
|
|
308
|
-
{{ "type": "bash", "command": "firefox &" }},
|
|
309
|
-
{{ "type": "wait", "duration": 3 }},
|
|
310
|
-
{{ "type": "type", "text": "news about space exploration" }},
|
|
311
|
-
{{ "type": "key", "keys": ["enter"] }},
|
|
312
|
-
]
|
|
313
|
-
}}
|
|
314
|
-
|
|
315
|
-
---
|
|
316
|
-
|
|
317
|
-
Once a task has been verified and completed, your action list should only be
|
|
318
|
-
{{
|
|
319
|
-
"actions": [
|
|
320
|
-
{{ "type": "quit" }}
|
|
321
|
-
]
|
|
322
|
-
}}
|
|
323
|
-
"""
|
|
324
|
-
|
|
325
|
-
screenshot_path = capture_screenshot(npc=npc, full=True).get('file_path')
|
|
326
|
-
if not screenshot_path:
|
|
327
|
-
time.sleep(2)
|
|
328
|
-
continue
|
|
329
|
-
|
|
330
|
-
image_to_send_path = screenshot_path
|
|
331
|
-
if last_click_coords:
|
|
332
|
-
try:
|
|
333
|
-
img = Image.open(screenshot_path)
|
|
334
|
-
draw = ImageDraw.Draw(img)
|
|
335
|
-
width, height = img.size
|
|
336
|
-
x_pixel = int(last_click_coords['x'] * width / 100)
|
|
337
|
-
y_pixel = int(last_click_coords['y'] * height / 100)
|
|
338
|
-
|
|
339
|
-
try:
|
|
340
|
-
font = ImageFont.truetype("DejaVuSans-Bold.ttf", size=48)
|
|
341
|
-
except IOError:
|
|
342
|
-
font = ImageFont.load_default()
|
|
343
|
-
|
|
344
|
-
draw.text((x_pixel - 8, y_pixel - 12),
|
|
345
|
-
f"+{last_click_coords['x'],last_click_coords['y']}",
|
|
346
|
-
fill="red",
|
|
347
|
-
font=font)
|
|
348
|
-
|
|
349
|
-
marked_image_path = "/tmp/marked_screenshot.png"
|
|
350
|
-
img.save(marked_image_path)
|
|
351
|
-
image_to_send_path = marked_image_path
|
|
352
|
-
print(f"Drew marker at ({x_pixel}, {y_pixel}) on new screenshot.")
|
|
353
|
-
except Exception as e:
|
|
354
|
-
print(f"Failed to draw marker on image: {e}")
|
|
355
|
-
|
|
356
|
-
response = get_llm_response(
|
|
357
|
-
prompt=prompt_template,
|
|
358
|
-
model=model,
|
|
359
|
-
provider=provider,
|
|
360
|
-
npc=npc,
|
|
361
|
-
images=[image_to_send_path],
|
|
362
|
-
messages=messages,
|
|
363
|
-
format="json",
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
if "messages" in response:
|
|
367
|
-
messages = response["messages"]
|
|
368
|
-
|
|
369
|
-
response_data = response.get('response')
|
|
370
|
-
|
|
371
|
-
if not isinstance(response_data, dict) or "actions" not in response_data:
|
|
372
|
-
last_action_feedback = f"Invalid JSON response from model: {response_data}"
|
|
373
|
-
continue
|
|
374
|
-
|
|
375
|
-
actions_list = response_data.get("actions", [])
|
|
376
|
-
|
|
377
|
-
if not isinstance(actions_list, list):
|
|
378
|
-
last_action_feedback = "Model did not return a list in the 'actions' key."
|
|
379
|
-
continue
|
|
380
|
-
|
|
381
|
-
last_click_coords = None
|
|
382
|
-
for action in actions_list:
|
|
383
|
-
if debug:
|
|
384
|
-
print(f"Executing action: {action}")
|
|
385
|
-
if action.get("type") == "quit":
|
|
386
|
-
print("Task complete: Model returned 'quit' action.")
|
|
387
|
-
return synthesized_summary
|
|
388
|
-
|
|
389
|
-
result = perform_action(action)
|
|
390
|
-
last_action_feedback = result.get("message") or result.get("output")
|
|
391
|
-
|
|
392
|
-
if action.get("type") == "click":
|
|
393
|
-
last_click_coords = {"x": action.get("x"), "y": action.get("y")}
|
|
394
|
-
|
|
395
|
-
if result.get("status") == "error":
|
|
396
|
-
print(f"Action failed, providing feedback to model: {last_action_feedback}")
|
|
397
|
-
break
|
|
398
|
-
time.sleep(1)
|
|
399
|
-
|
|
400
|
-
if not actions_list:
|
|
401
|
-
last_action_feedback = "No actions were returned. The task is likely not complete. Re-evaluating."
|
|
402
|
-
print(last_action_feedback)
|
|
403
|
-
|
|
404
|
-
iteration_count += 1
|
|
405
|
-
return synthesized_summary
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if __name__ == "__main__":
|
|
409
|
-
repl_loop()
|
npcpy/modes/pti.py
DELETED
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
# pti
|
|
3
|
-
import json
|
|
4
|
-
from typing import Dict, List, Optional, Any, Generator
|
|
5
|
-
import os
|
|
6
|
-
from npcpy.memory.command_history import CommandHistory, save_attachment_to_message, start_new_conversation,save_conversation_message
|
|
7
|
-
from npcpy.npc_sysenv import (NPCSH_REASONING_MODEL,
|
|
8
|
-
NPCSH_REASONING_PROVIDER,
|
|
9
|
-
NPCSH_CHAT_MODEL,
|
|
10
|
-
NPCSH_CHAT_PROVIDER,
|
|
11
|
-
NPCSH_API_URL,
|
|
12
|
-
NPCSH_STREAM_OUTPUT,print_and_process_stream_with_markdown)
|
|
13
|
-
from npcpy.llm_funcs import get_llm_response, handle_request_input
|
|
14
|
-
|
|
15
|
-
from npcpy.npc_compiler import NPC
|
|
16
|
-
from npcpy.data.load import load_csv, load_pdf
|
|
17
|
-
from npcpy.data.text import rag_search
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def enter_reasoning_human_in_the_loop(
|
|
25
|
-
user_input=None,
|
|
26
|
-
messages: List[Dict[str, str]] = None,
|
|
27
|
-
reasoning_model: str = NPCSH_REASONING_MODEL,
|
|
28
|
-
reasoning_provider: str = NPCSH_REASONING_PROVIDER,
|
|
29
|
-
files : List = None,
|
|
30
|
-
npc: Any = None,
|
|
31
|
-
conversation_id : str= False,
|
|
32
|
-
answer_only: bool = False,
|
|
33
|
-
context=None,
|
|
34
|
-
) :
|
|
35
|
-
"""
|
|
36
|
-
Stream responses while checking for think tokens and handling human input when needed.
|
|
37
|
-
|
|
38
|
-
Args:
|
|
39
|
-
messages: List of conversation messages
|
|
40
|
-
model: LLM model to use
|
|
41
|
-
provider: Model provider
|
|
42
|
-
npc: NPC instance if applicable
|
|
43
|
-
|
|
44
|
-
"""
|
|
45
|
-
# Get the initial stream
|
|
46
|
-
loaded_content = {} # New dictionary to hold loaded content
|
|
47
|
-
|
|
48
|
-
# Create conversation ID if not provided
|
|
49
|
-
if not conversation_id:
|
|
50
|
-
conversation_id = start_new_conversation()
|
|
51
|
-
|
|
52
|
-
command_history = CommandHistory()
|
|
53
|
-
# Load specified files if any
|
|
54
|
-
if files:
|
|
55
|
-
for file in files:
|
|
56
|
-
extension = os.path.splitext(file)[1].lower()
|
|
57
|
-
try:
|
|
58
|
-
if extension == ".pdf":
|
|
59
|
-
content = load_pdf(file)["texts"].iloc[0]
|
|
60
|
-
elif extension == ".csv":
|
|
61
|
-
content = load_csv(file)
|
|
62
|
-
else:
|
|
63
|
-
print(f"Unsupported file type: {file}")
|
|
64
|
-
continue
|
|
65
|
-
loaded_content[file] = content
|
|
66
|
-
print(f"Loaded content from: {file}")
|
|
67
|
-
except Exception as e:
|
|
68
|
-
print(f"Error loading {file}: {str(e)}")
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
try:
|
|
72
|
-
while True:
|
|
73
|
-
|
|
74
|
-
if loaded_content:
|
|
75
|
-
context_content = ""
|
|
76
|
-
for filename, content in loaded_content.items():
|
|
77
|
-
retrieved_docs = rag_search(
|
|
78
|
-
user_input,
|
|
79
|
-
content,
|
|
80
|
-
)
|
|
81
|
-
if retrieved_docs:
|
|
82
|
-
context_content += (
|
|
83
|
-
f"\n\nLoaded content from: {filename}\n{content}\n\n"
|
|
84
|
-
)
|
|
85
|
-
if len(context_content) > 0:
|
|
86
|
-
user_input += f"""
|
|
87
|
-
Here is the loaded content that may be relevant to your query:
|
|
88
|
-
{context_content}
|
|
89
|
-
Please reference it explicitly in your response and use it for answering.
|
|
90
|
-
"""
|
|
91
|
-
if answer_only:
|
|
92
|
-
response = get_llm_response(
|
|
93
|
-
user_input,
|
|
94
|
-
model = reasoning_model,
|
|
95
|
-
provider=reasoning_provider,
|
|
96
|
-
messages=messages,
|
|
97
|
-
stream=True,
|
|
98
|
-
)
|
|
99
|
-
assistant_reply, messages = response['response'], response['messages']
|
|
100
|
-
assistant_reply = print_and_process_stream_with_markdown(assistant_reply, reasoning_model, reasoning_provider)
|
|
101
|
-
messages.append({'role':'assistant', 'content':assistant_reply})
|
|
102
|
-
return enter_reasoning_human_in_the_loop(user_input = None,
|
|
103
|
-
messages=messages,
|
|
104
|
-
reasoning_model=reasoning_model,
|
|
105
|
-
reasoning_provider=reasoning_provider, answer_only=False)
|
|
106
|
-
else:
|
|
107
|
-
message= "Think first though and use <think> tags in your chain of thought. Once finished, either answer plainly or write a request for input by beginning with the <request_for_input> tag. and close it with a </request_for_input>"
|
|
108
|
-
if user_input is None:
|
|
109
|
-
user_input = input('🐻❄️>')
|
|
110
|
-
|
|
111
|
-
message_id = save_conversation_message(
|
|
112
|
-
command_history,
|
|
113
|
-
conversation_id,
|
|
114
|
-
"user",
|
|
115
|
-
user_input,
|
|
116
|
-
wd=os.getcwd(),
|
|
117
|
-
model=reasoning_model,
|
|
118
|
-
provider=reasoning_provider,
|
|
119
|
-
npc=npc.name if npc else None,
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
response = get_llm_response(
|
|
123
|
-
user_input+message,
|
|
124
|
-
model = reasoning_model,
|
|
125
|
-
provider=reasoning_provider,
|
|
126
|
-
messages=messages,
|
|
127
|
-
stream=True,
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
assistant_reply, messages = response['response'], response['messages']
|
|
131
|
-
thoughts = []
|
|
132
|
-
response_chunks = []
|
|
133
|
-
in_think_block = False # the thinking chain generated after reasoning
|
|
134
|
-
|
|
135
|
-
thinking = False # the reasoning content
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
for chunk in assistant_reply:
|
|
139
|
-
if thinking:
|
|
140
|
-
if not in_think_block:
|
|
141
|
-
in_think_block = True
|
|
142
|
-
try:
|
|
143
|
-
|
|
144
|
-
if reasoning_provider == "ollama":
|
|
145
|
-
chunk_content = chunk.get("message", {}).get("content", "")
|
|
146
|
-
else:
|
|
147
|
-
chunk_content = ''
|
|
148
|
-
reasoning_content = ''
|
|
149
|
-
for c in chunk.choices:
|
|
150
|
-
if hasattr(c.delta, "reasoning_content"):
|
|
151
|
-
|
|
152
|
-
reasoning_content += c.delta.reasoning_content
|
|
153
|
-
|
|
154
|
-
if reasoning_content:
|
|
155
|
-
thinking = True
|
|
156
|
-
chunk_content = reasoning_content
|
|
157
|
-
chunk_content += "".join(
|
|
158
|
-
choice.delta.content
|
|
159
|
-
for choice in chunk.choices
|
|
160
|
-
if choice.delta.content is not None
|
|
161
|
-
)
|
|
162
|
-
response_chunks.append(chunk_content)
|
|
163
|
-
print(chunk_content, end='')
|
|
164
|
-
combined_text = "".join(response_chunks)
|
|
165
|
-
|
|
166
|
-
if in_think_block:
|
|
167
|
-
if '</thinking>' in combined_text:
|
|
168
|
-
in_think_block = False
|
|
169
|
-
thoughts.append(chunk_content)
|
|
170
|
-
|
|
171
|
-
if "</request_for_input>" in combined_text:
|
|
172
|
-
# Process the LLM's input request
|
|
173
|
-
request_text = "".join(thoughts)
|
|
174
|
-
|
|
175
|
-
print("\nPlease provide the requested information: ")
|
|
176
|
-
|
|
177
|
-
user_input = input('🐻❄️>')
|
|
178
|
-
|
|
179
|
-
messages.append({"role": "assistant", "content": request_text})
|
|
180
|
-
|
|
181
|
-
print("\n[Continuing with provided information...]\n")
|
|
182
|
-
return enter_reasoning_human_in_the_loop( user_input = user_input,
|
|
183
|
-
messages=messages,
|
|
184
|
-
reasoning_model=reasoning_model,
|
|
185
|
-
reasoning_provider=reasoning_provider,
|
|
186
|
-
npc=npc,
|
|
187
|
-
answer_only=True)
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
except KeyboardInterrupt:
|
|
191
|
-
user_interrupt = input("\n[Stream interrupted by user]\n Enter your additional input: ")
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
# Add the interruption to messages and restart stream
|
|
195
|
-
messages.append(
|
|
196
|
-
{"role": "user", "content": f"[INTERRUPT] {user_interrupt}"}
|
|
197
|
-
)
|
|
198
|
-
print(f"\n[Continuing with added context...]\n")
|
|
199
|
-
|
|
200
|
-
except KeyboardInterrupt:
|
|
201
|
-
user_interrupt = input("\n[Stream interrupted by user]\n 🔴🔴🔴🔴\nEnter your additional input: ")
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
# Add the interruption to messages and restart stream
|
|
205
|
-
messages.append(
|
|
206
|
-
{"role": "user", "content": f"[INTERRUPT] {user_interrupt}"}
|
|
207
|
-
)
|
|
208
|
-
print(f"\n[Continuing with added context...]\n")
|
|
209
|
-
|
|
210
|
-
return {'messages':messages, }
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def main():
|
|
214
|
-
# Example usage
|
|
215
|
-
import argparse
|
|
216
|
-
parser = argparse.ArgumentParser(description="Enter PTI mode for chatting with an LLM")
|
|
217
|
-
parser.add_argument("--npc", default='~/.npcsh/npc_team/frederic.npc', help="Path to NPC File")
|
|
218
|
-
parser.add_argument("--model", default=NPCSH_REASONING_MODEL, help="Model to use")
|
|
219
|
-
parser.add_argument("--provider", default=NPCSH_REASONING_PROVIDER, help="Provider to use")
|
|
220
|
-
parser.add_argument("--files", nargs="*", help="Files to load into context")
|
|
221
|
-
args = parser.parse_args()
|
|
222
|
-
|
|
223
|
-
npc = NPC(file=args.npc)
|
|
224
|
-
enter_reasoning_human_in_the_loop(
|
|
225
|
-
messages = [],
|
|
226
|
-
npc=npc,
|
|
227
|
-
reasoning_model=args.model,
|
|
228
|
-
reasoning_provider=args.provider,
|
|
229
|
-
files=args.files,
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
if __name__ == "__main__":
|
|
233
|
-
main()
|
|
234
|
-
|