npcpy 1.0.26__py3-none-any.whl → 1.2.32__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. npcpy/__init__.py +0 -7
  2. npcpy/data/audio.py +16 -99
  3. npcpy/data/image.py +43 -42
  4. npcpy/data/load.py +83 -124
  5. npcpy/data/text.py +28 -28
  6. npcpy/data/video.py +8 -32
  7. npcpy/data/web.py +51 -23
  8. npcpy/ft/diff.py +110 -0
  9. npcpy/ft/ge.py +115 -0
  10. npcpy/ft/memory_trainer.py +171 -0
  11. npcpy/ft/model_ensembler.py +357 -0
  12. npcpy/ft/rl.py +360 -0
  13. npcpy/ft/sft.py +248 -0
  14. npcpy/ft/usft.py +128 -0
  15. npcpy/gen/audio_gen.py +24 -0
  16. npcpy/gen/embeddings.py +13 -13
  17. npcpy/gen/image_gen.py +262 -117
  18. npcpy/gen/response.py +615 -415
  19. npcpy/gen/video_gen.py +53 -7
  20. npcpy/llm_funcs.py +1869 -437
  21. npcpy/main.py +1 -1
  22. npcpy/memory/command_history.py +844 -510
  23. npcpy/memory/kg_vis.py +833 -0
  24. npcpy/memory/knowledge_graph.py +892 -1845
  25. npcpy/memory/memory_processor.py +81 -0
  26. npcpy/memory/search.py +188 -90
  27. npcpy/mix/debate.py +192 -3
  28. npcpy/npc_compiler.py +1672 -801
  29. npcpy/npc_sysenv.py +593 -1266
  30. npcpy/serve.py +3120 -0
  31. npcpy/sql/ai_function_tools.py +257 -0
  32. npcpy/sql/database_ai_adapters.py +186 -0
  33. npcpy/sql/database_ai_functions.py +163 -0
  34. npcpy/sql/model_runner.py +19 -19
  35. npcpy/sql/npcsql.py +706 -507
  36. npcpy/sql/sql_model_compiler.py +156 -0
  37. npcpy/tools.py +183 -0
  38. npcpy/work/plan.py +13 -279
  39. npcpy/work/trigger.py +3 -3
  40. npcpy-1.2.32.dist-info/METADATA +803 -0
  41. npcpy-1.2.32.dist-info/RECORD +54 -0
  42. npcpy/data/dataframes.py +0 -171
  43. npcpy/memory/deep_research.py +0 -125
  44. npcpy/memory/sleep.py +0 -557
  45. npcpy/modes/_state.py +0 -78
  46. npcpy/modes/alicanto.py +0 -1075
  47. npcpy/modes/guac.py +0 -785
  48. npcpy/modes/mcp_npcsh.py +0 -822
  49. npcpy/modes/npc.py +0 -213
  50. npcpy/modes/npcsh.py +0 -1158
  51. npcpy/modes/plonk.py +0 -409
  52. npcpy/modes/pti.py +0 -234
  53. npcpy/modes/serve.py +0 -1637
  54. npcpy/modes/spool.py +0 -312
  55. npcpy/modes/wander.py +0 -549
  56. npcpy/modes/yap.py +0 -572
  57. npcpy/npc_team/alicanto.npc +0 -2
  58. npcpy/npc_team/alicanto.png +0 -0
  59. npcpy/npc_team/assembly_lines/test_pipeline.py +0 -181
  60. npcpy/npc_team/corca.npc +0 -13
  61. npcpy/npc_team/foreman.npc +0 -7
  62. npcpy/npc_team/frederic.npc +0 -6
  63. npcpy/npc_team/frederic4.png +0 -0
  64. npcpy/npc_team/guac.png +0 -0
  65. npcpy/npc_team/jinxs/automator.jinx +0 -18
  66. npcpy/npc_team/jinxs/bash_executer.jinx +0 -31
  67. npcpy/npc_team/jinxs/calculator.jinx +0 -11
  68. npcpy/npc_team/jinxs/edit_file.jinx +0 -96
  69. npcpy/npc_team/jinxs/file_chat.jinx +0 -14
  70. npcpy/npc_team/jinxs/gui_controller.jinx +0 -28
  71. npcpy/npc_team/jinxs/image_generation.jinx +0 -29
  72. npcpy/npc_team/jinxs/internet_search.jinx +0 -30
  73. npcpy/npc_team/jinxs/local_search.jinx +0 -152
  74. npcpy/npc_team/jinxs/npcsh_executor.jinx +0 -31
  75. npcpy/npc_team/jinxs/python_executor.jinx +0 -8
  76. npcpy/npc_team/jinxs/screen_cap.jinx +0 -25
  77. npcpy/npc_team/jinxs/sql_executor.jinx +0 -33
  78. npcpy/npc_team/kadiefa.npc +0 -3
  79. npcpy/npc_team/kadiefa.png +0 -0
  80. npcpy/npc_team/npcsh.ctx +0 -9
  81. npcpy/npc_team/npcsh_sibiji.png +0 -0
  82. npcpy/npc_team/plonk.npc +0 -2
  83. npcpy/npc_team/plonk.png +0 -0
  84. npcpy/npc_team/plonkjr.npc +0 -2
  85. npcpy/npc_team/plonkjr.png +0 -0
  86. npcpy/npc_team/sibiji.npc +0 -5
  87. npcpy/npc_team/sibiji.png +0 -0
  88. npcpy/npc_team/spool.png +0 -0
  89. npcpy/npc_team/templates/analytics/celona.npc +0 -0
  90. npcpy/npc_team/templates/hr_support/raone.npc +0 -0
  91. npcpy/npc_team/templates/humanities/eriane.npc +0 -4
  92. npcpy/npc_team/templates/it_support/lineru.npc +0 -0
  93. npcpy/npc_team/templates/marketing/slean.npc +0 -4
  94. npcpy/npc_team/templates/philosophy/maurawa.npc +0 -0
  95. npcpy/npc_team/templates/sales/turnic.npc +0 -4
  96. npcpy/npc_team/templates/software/welxor.npc +0 -0
  97. npcpy/npc_team/yap.png +0 -0
  98. npcpy/routes.py +0 -958
  99. npcpy/work/mcp_helpers.py +0 -357
  100. npcpy/work/mcp_server.py +0 -194
  101. npcpy-1.0.26.data/data/npcpy/npc_team/alicanto.npc +0 -2
  102. npcpy-1.0.26.data/data/npcpy/npc_team/alicanto.png +0 -0
  103. npcpy-1.0.26.data/data/npcpy/npc_team/automator.jinx +0 -18
  104. npcpy-1.0.26.data/data/npcpy/npc_team/bash_executer.jinx +0 -31
  105. npcpy-1.0.26.data/data/npcpy/npc_team/calculator.jinx +0 -11
  106. npcpy-1.0.26.data/data/npcpy/npc_team/celona.npc +0 -0
  107. npcpy-1.0.26.data/data/npcpy/npc_team/corca.npc +0 -13
  108. npcpy-1.0.26.data/data/npcpy/npc_team/edit_file.jinx +0 -96
  109. npcpy-1.0.26.data/data/npcpy/npc_team/eriane.npc +0 -4
  110. npcpy-1.0.26.data/data/npcpy/npc_team/file_chat.jinx +0 -14
  111. npcpy-1.0.26.data/data/npcpy/npc_team/foreman.npc +0 -7
  112. npcpy-1.0.26.data/data/npcpy/npc_team/frederic.npc +0 -6
  113. npcpy-1.0.26.data/data/npcpy/npc_team/frederic4.png +0 -0
  114. npcpy-1.0.26.data/data/npcpy/npc_team/guac.png +0 -0
  115. npcpy-1.0.26.data/data/npcpy/npc_team/gui_controller.jinx +0 -28
  116. npcpy-1.0.26.data/data/npcpy/npc_team/image_generation.jinx +0 -29
  117. npcpy-1.0.26.data/data/npcpy/npc_team/internet_search.jinx +0 -30
  118. npcpy-1.0.26.data/data/npcpy/npc_team/kadiefa.npc +0 -3
  119. npcpy-1.0.26.data/data/npcpy/npc_team/kadiefa.png +0 -0
  120. npcpy-1.0.26.data/data/npcpy/npc_team/lineru.npc +0 -0
  121. npcpy-1.0.26.data/data/npcpy/npc_team/local_search.jinx +0 -152
  122. npcpy-1.0.26.data/data/npcpy/npc_team/maurawa.npc +0 -0
  123. npcpy-1.0.26.data/data/npcpy/npc_team/npcsh.ctx +0 -9
  124. npcpy-1.0.26.data/data/npcpy/npc_team/npcsh_executor.jinx +0 -31
  125. npcpy-1.0.26.data/data/npcpy/npc_team/npcsh_sibiji.png +0 -0
  126. npcpy-1.0.26.data/data/npcpy/npc_team/plonk.npc +0 -2
  127. npcpy-1.0.26.data/data/npcpy/npc_team/plonk.png +0 -0
  128. npcpy-1.0.26.data/data/npcpy/npc_team/plonkjr.npc +0 -2
  129. npcpy-1.0.26.data/data/npcpy/npc_team/plonkjr.png +0 -0
  130. npcpy-1.0.26.data/data/npcpy/npc_team/python_executor.jinx +0 -8
  131. npcpy-1.0.26.data/data/npcpy/npc_team/raone.npc +0 -0
  132. npcpy-1.0.26.data/data/npcpy/npc_team/screen_cap.jinx +0 -25
  133. npcpy-1.0.26.data/data/npcpy/npc_team/sibiji.npc +0 -5
  134. npcpy-1.0.26.data/data/npcpy/npc_team/sibiji.png +0 -0
  135. npcpy-1.0.26.data/data/npcpy/npc_team/slean.npc +0 -4
  136. npcpy-1.0.26.data/data/npcpy/npc_team/spool.png +0 -0
  137. npcpy-1.0.26.data/data/npcpy/npc_team/sql_executor.jinx +0 -33
  138. npcpy-1.0.26.data/data/npcpy/npc_team/test_pipeline.py +0 -181
  139. npcpy-1.0.26.data/data/npcpy/npc_team/turnic.npc +0 -4
  140. npcpy-1.0.26.data/data/npcpy/npc_team/welxor.npc +0 -0
  141. npcpy-1.0.26.data/data/npcpy/npc_team/yap.png +0 -0
  142. npcpy-1.0.26.dist-info/METADATA +0 -827
  143. npcpy-1.0.26.dist-info/RECORD +0 -139
  144. npcpy-1.0.26.dist-info/entry_points.txt +0 -11
  145. /npcpy/{modes → ft}/__init__.py +0 -0
  146. {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/WHEEL +0 -0
  147. {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/licenses/LICENSE +0 -0
  148. {npcpy-1.0.26.dist-info → npcpy-1.2.32.dist-info}/top_level.txt +0 -0
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
-