praisonaiagents 0.0.11__tar.gz → 0.0.13__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.
Files changed (24) hide show
  1. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/PKG-INFO +1 -1
  2. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/agent/agent.py +63 -23
  3. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/agents/agents.py +81 -1
  4. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/main.py +64 -15
  5. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/task/task.py +3 -1
  6. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents.egg-info/PKG-INFO +1 -1
  7. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/pyproject.toml +1 -1
  8. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/__init__.py +0 -0
  9. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/agent/__init__.py +0 -0
  10. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/agents/__init__.py +0 -0
  11. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/__init__.py +0 -0
  12. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/agent/__init__.py +0 -0
  13. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/agent/agent.py +0 -0
  14. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/agents/__init__.py +0 -0
  15. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/agents/agents.py +0 -0
  16. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/main.py +0 -0
  17. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/task/__init__.py +0 -0
  18. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/task/task.py +0 -0
  19. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents/task/__init__.py +0 -0
  20. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents.egg-info/SOURCES.txt +0 -0
  21. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents.egg-info/dependency_links.txt +0 -0
  22. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents.egg-info/requires.txt +0 -0
  23. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/praisonaiagents.egg-info/top_level.txt +0 -0
  24. {praisonaiagents-0.0.11 → praisonaiagents-0.0.13}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: praisonaiagents
3
- Version: 0.0.11
3
+ Version: 0.0.13
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Dist: pydantic
@@ -140,7 +140,7 @@ class Agent:
140
140
  max_rpm: Optional[int] = None,
141
141
  max_execution_time: Optional[int] = None,
142
142
  memory: bool = True,
143
- verbose: bool = False,
143
+ verbose: bool = True,
144
144
  allow_delegation: bool = False,
145
145
  step_callback: Optional[Any] = None,
146
146
  cache: bool = True,
@@ -191,26 +191,44 @@ class Agent:
191
191
  self.max_reflect = max_reflect
192
192
  self.min_reflect = min_reflect
193
193
  self.reflect_llm = reflect_llm
194
+ self.console = Console() # Create a single console instance for the agent
195
+
194
196
  def execute_tool(self, function_name, arguments):
195
197
  """
196
198
  Execute a tool dynamically based on the function name and arguments.
197
199
  """
198
200
  logging.debug(f"{self.name} executing tool {function_name} with arguments: {arguments}")
199
201
 
200
- # Try to get the function from globals first
201
- func = globals().get(function_name)
202
+ # Try to find the function in the agent's tools list first
203
+ func = None
204
+ for tool in self.tools:
205
+ if callable(tool) and getattr(tool, '__name__', '') == function_name:
206
+ func = tool
207
+ break
208
+
209
+ logging.debug(f"Looking for {function_name} in agent tools: {func is not None}")
210
+
211
+ # If not found in tools, try globals and main
202
212
  if not func:
203
- # Then try to get from the main module
204
- import __main__
205
- func = getattr(__main__, function_name, None)
213
+ func = globals().get(function_name)
214
+ logging.debug(f"Looking for {function_name} in globals: {func is not None}")
215
+
216
+ if not func:
217
+ import __main__
218
+ func = getattr(__main__, function_name, None)
219
+ logging.debug(f"Looking for {function_name} in __main__: {func is not None}")
206
220
 
207
221
  if func and callable(func):
208
222
  try:
209
223
  return func(**arguments)
210
224
  except Exception as e:
211
- return {"error": str(e)}
225
+ error_msg = str(e)
226
+ logging.error(f"Error executing tool {function_name}: {error_msg}")
227
+ return {"error": error_msg}
212
228
 
213
- return {"error": f"Tool '{function_name}' is not callable"}
229
+ error_msg = f"Tool '{function_name}' is not callable"
230
+ logging.error(error_msg)
231
+ return {"error": error_msg}
214
232
 
215
233
  def clear_history(self):
216
234
  self.chat_history = []
@@ -219,7 +237,6 @@ class Agent:
219
237
  return f"Agent(name='{self.name}', role='{self.role}', goal='{self.goal}')"
220
238
 
221
239
  def _chat_completion(self, messages, temperature=0.2, tools=None, stream=True):
222
- console = Console()
223
240
  start_time = time.time()
224
241
  logging.debug(f"{self.name} sending messages to LLM: {messages}")
225
242
 
@@ -289,12 +306,24 @@ class Agent:
289
306
  stream=True
290
307
  )
291
308
  full_response_text = ""
292
- with Live(display_generating("", start_time), refresh_per_second=4) as live:
309
+
310
+ # Create Live display with proper configuration
311
+ with Live(
312
+ display_generating("", start_time),
313
+ console=self.console,
314
+ refresh_per_second=4,
315
+ transient=False, # Changed to False to preserve output
316
+ vertical_overflow="ellipsis",
317
+ auto_refresh=True
318
+ ) as live:
293
319
  for chunk in response_stream:
294
320
  if chunk.choices[0].delta.content:
295
321
  full_response_text += chunk.choices[0].delta.content
296
322
  live.update(display_generating(full_response_text, start_time))
297
-
323
+
324
+ # Clear the last generating display with a blank line
325
+ self.console.print()
326
+
298
327
  final_response = client.chat.completions.create(
299
328
  model=self.llm,
300
329
  messages=messages,
@@ -331,7 +360,11 @@ Your Goal: {self.goal}
331
360
  if system_prompt:
332
361
  messages.append({"role": "system", "content": system_prompt})
333
362
  messages.extend(self.chat_history)
334
- messages.append({"role": "user", "content": prompt})
363
+ if isinstance(prompt, list):
364
+ # If we receive a multimodal prompt list, place it directly in the user message
365
+ messages.append({"role": "user", "content": prompt})
366
+ else:
367
+ messages.append({"role": "user", "content": prompt})
335
368
 
336
369
  final_response_text = None
337
370
  reflection_count = 0
@@ -340,7 +373,14 @@ Your Goal: {self.goal}
340
373
  while True:
341
374
  try:
342
375
  if self.verbose:
343
- display_instruction(f"Agent {self.name} is processing prompt: {prompt}")
376
+ # Handle both string and list prompts for instruction display
377
+ display_text = prompt
378
+ if isinstance(prompt, list):
379
+ # Extract text content from multimodal prompt
380
+ display_text = next((item["text"] for item in prompt if item["type"] == "text"), "")
381
+
382
+ if display_text and str(display_text).strip():
383
+ display_instruction(f"Agent {self.name} is processing prompt: {display_text}", console=self.console)
344
384
 
345
385
  response = self._chat_completion(messages, temperature=temperature, tools=tools if tools else None)
346
386
  if not response:
@@ -360,13 +400,13 @@ Your Goal: {self.goal}
360
400
  arguments = json.loads(tool_call.function.arguments)
361
401
 
362
402
  if self.verbose:
363
- display_tool_call(f"Agent {self.name} is calling function '{function_name}' with arguments: {arguments}")
403
+ display_tool_call(f"Agent {self.name} is calling function '{function_name}' with arguments: {arguments}", console=self.console)
364
404
 
365
405
  tool_result = self.execute_tool(function_name, arguments)
366
406
 
367
407
  if tool_result:
368
408
  if self.verbose:
369
- display_tool_call(f"Function '{function_name}' returned: {tool_result}")
409
+ display_tool_call(f"Function '{function_name}' returned: {tool_result}", console=self.console)
370
410
  messages.append({
371
411
  "role": "tool",
372
412
  "tool_call_id": tool_call.id,
@@ -391,7 +431,7 @@ Your Goal: {self.goal}
391
431
  self.chat_history.append({"role": "assistant", "content": response_text})
392
432
  if self.verbose:
393
433
  logging.info(f"Agent {self.name} final response: {response_text}")
394
- display_interaction(prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time)
434
+ display_interaction(prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time, console=self.console)
395
435
  return response_text
396
436
 
397
437
  reflection_prompt = f"""
@@ -414,26 +454,26 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
414
454
  reflection_output = reflection_response.choices[0].message.parsed
415
455
 
416
456
  if self.verbose:
417
- display_self_reflection(f"Agent {self.name} self reflection (using {self.reflect_llm if self.reflect_llm else self.llm}): reflection='{reflection_output.reflection}' satisfactory='{reflection_output.satisfactory}'")
457
+ display_self_reflection(f"Agent {self.name} self reflection (using {self.reflect_llm if self.reflect_llm else self.llm}): reflection='{reflection_output.reflection}' satisfactory='{reflection_output.satisfactory}'", console=self.console)
418
458
 
419
459
  messages.append({"role": "assistant", "content": f"Self Reflection: {reflection_output.reflection} Satisfactory?: {reflection_output.satisfactory}"})
420
460
 
421
461
  # Only consider satisfactory after minimum reflections
422
462
  if reflection_output.satisfactory == "yes" and reflection_count >= self.min_reflect - 1:
423
463
  if self.verbose:
424
- display_self_reflection("Agent marked the response as satisfactory after meeting minimum reflections")
464
+ display_self_reflection("Agent marked the response as satisfactory after meeting minimum reflections", console=self.console)
425
465
  self.chat_history.append({"role": "user", "content": prompt})
426
466
  self.chat_history.append({"role": "assistant", "content": response_text})
427
- display_interaction(prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time)
467
+ display_interaction(prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time, console=self.console)
428
468
  return response_text
429
469
 
430
470
  # Check if we've hit max reflections
431
471
  if reflection_count >= self.max_reflect - 1:
432
472
  if self.verbose:
433
- display_self_reflection("Maximum reflection count reached, returning current response")
473
+ display_self_reflection("Maximum reflection count reached, returning current response", console=self.console)
434
474
  self.chat_history.append({"role": "user", "content": prompt})
435
475
  self.chat_history.append({"role": "assistant", "content": response_text})
436
- display_interaction(prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time)
476
+ display_interaction(prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time, console=self.console)
437
477
  return response_text
438
478
 
439
479
  logging.debug(f"{self.name} reflection count {reflection_count + 1}, continuing reflection process")
@@ -444,12 +484,12 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
444
484
  continue # Continue the loop for more reflections
445
485
 
446
486
  except Exception as e:
447
- display_error(f"Error in parsing self-reflection json {e}. Retrying")
487
+ display_error(f"Error in parsing self-reflection json {e}. Retrying", console=self.console)
448
488
  logging.error("Reflection parsing failed.", exc_info=True)
449
489
  messages.append({"role": "assistant", "content": f"Self Reflection failed."})
450
490
  reflection_count += 1
451
491
  continue # Continue even after error to try again
452
492
 
453
493
  except Exception as e:
454
- display_error(f"Error in chat: {e}")
494
+ display_error(f"Error in chat: {e}", console=self.console)
455
495
  return None
@@ -11,6 +11,33 @@ from ..main import display_error, TaskOutput, error_logs, client
11
11
  from ..agent.agent import Agent
12
12
  from ..task.task import Task
13
13
 
14
+ def encode_file_to_base64(file_path: str) -> str:
15
+ """Base64-encode a file."""
16
+ import base64
17
+ with open(file_path, "rb") as f:
18
+ return base64.b64encode(f.read()).decode("utf-8")
19
+
20
+ def process_video(video_path: str, seconds_per_frame=2):
21
+ """Split video into frames (base64-encoded)."""
22
+ import cv2
23
+ import base64
24
+ base64_frames = []
25
+ video = cv2.VideoCapture(video_path)
26
+ total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
27
+ fps = video.get(cv2.CAP_PROP_FPS)
28
+ frames_to_skip = int(fps * seconds_per_frame)
29
+ curr_frame = 0
30
+ while curr_frame < total_frames:
31
+ video.set(cv2.CAP_PROP_POS_FRAMES, curr_frame)
32
+ success, frame = video.read()
33
+ if not success:
34
+ break
35
+ _, buffer = cv2.imencode(".jpg", frame)
36
+ base64_frames.append(base64.b64encode(buffer).decode("utf-8"))
37
+ curr_frame += frames_to_skip
38
+ video.release()
39
+ return base64_frames
40
+
14
41
  class PraisonAIAgents:
15
42
  def __init__(self, agents, tasks, verbose=0, completion_checker=None, max_retries=5, process="sequential", manager_llm=None):
16
43
  self.agents = agents
@@ -58,6 +85,19 @@ class PraisonAIAgents:
58
85
  display_error(f"Error: Task with ID {task_id} does not exist")
59
86
  return
60
87
  task = self.tasks[task_id]
88
+
89
+ # Only import multimodal dependencies if task has images
90
+ if task.images and task.status == "not started":
91
+ try:
92
+ import cv2
93
+ import base64
94
+ from moviepy import VideoFileClip
95
+ except ImportError as e:
96
+ display_error(f"Error: Missing required dependencies for image/video processing: {e}")
97
+ display_error("Please install with: pip install opencv-python moviepy")
98
+ task.status = "failed"
99
+ return None
100
+
61
101
  if task.status == "not started":
62
102
  task.status = "in progress"
63
103
 
@@ -83,7 +123,47 @@ Expected Output: {task.expected_output}.
83
123
  if self.verbose >= 2:
84
124
  logging.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
85
125
  logging.debug(f"Starting execution of task {task_id} with prompt:\n{task_prompt}")
86
- agent_output = executor_agent.chat(task_prompt, tools=task.tools)
126
+
127
+ if task.images:
128
+ def _get_multimodal_message(text_prompt, images):
129
+ content = [{"type": "text", "text": text_prompt}]
130
+
131
+ for img in images:
132
+ # If local file path for a valid image
133
+ if os.path.exists(img):
134
+ ext = os.path.splitext(img)[1].lower()
135
+ # If it's a .mp4, convert to frames
136
+ if ext == ".mp4":
137
+ frames = process_video(img, seconds_per_frame=1)
138
+ content.append({"type": "text", "text": "These are frames from the video."})
139
+ for f in frames:
140
+ content.append({
141
+ "type": "image_url",
142
+ "image_url": {"url": f"data:image/jpg;base64,{f}"}
143
+ })
144
+ else:
145
+ encoded = encode_file_to_base64(img)
146
+ content.append({
147
+ "type": "image_url",
148
+ "image_url": {
149
+ "url": f"data:image/{ext.lstrip('.')};base64,{encoded}"
150
+ }
151
+ })
152
+ else:
153
+ # Treat as a remote URL
154
+ content.append({
155
+ "type": "image_url",
156
+ "image_url": {"url": img}
157
+ })
158
+ return content
159
+
160
+ agent_output = executor_agent.chat(
161
+ _get_multimodal_message(task_prompt, task.images),
162
+ tools=task.tools
163
+ )
164
+ else:
165
+ agent_output = executor_agent.chat(task_prompt, tools=task.tools)
166
+
87
167
  if agent_output:
88
168
  task_output = TaskOutput(
89
169
  description=task.description,
@@ -25,43 +25,92 @@ logging.basicConfig(
25
25
  # Global list to store error logs
26
26
  error_logs = []
27
27
 
28
- def display_interaction(message: str, response: str, markdown: bool = True, generation_time: Optional[float] = None):
29
- console = Console()
30
- if generation_time is not None:
28
+ def _clean_display_content(content: str, max_length: int = 20000) -> str:
29
+ """Helper function to clean and truncate content for display."""
30
+ if not content or not str(content).strip():
31
+ return ""
32
+
33
+ content = str(content)
34
+ # Handle base64 content
35
+ if "base64" in content:
36
+ content_parts = []
37
+ for line in content.split('\n'):
38
+ if "base64" not in line:
39
+ content_parts.append(line)
40
+ content = '\n'.join(content_parts)
41
+
42
+ # Truncate if too long
43
+ if len(content) > max_length:
44
+ content = content[:max_length] + "..."
45
+
46
+ return content.strip()
47
+
48
+ def display_interaction(message, response, markdown=True, generation_time=None, console=None):
49
+ """Display the interaction between user and assistant."""
50
+ if console is None:
51
+ console = Console()
52
+ if generation_time:
31
53
  console.print(Text(f"Response generated in {generation_time:.1f}s", style="dim"))
32
- else:
33
- console.print(Text("Response Generation Complete", style="dim"))
54
+
55
+ # Handle multimodal content (list)
56
+ if isinstance(message, list):
57
+ # Extract just the text content from the multimodal message
58
+ text_content = next((item["text"] for item in message if item["type"] == "text"), "")
59
+ message = text_content
60
+
61
+ message = _clean_display_content(str(message))
62
+ response = _clean_display_content(str(response))
34
63
 
35
64
  if markdown:
36
65
  console.print(Panel.fit(Markdown(message), title="Message", border_style="cyan"))
37
66
  console.print(Panel.fit(Markdown(response), title="Response", border_style="cyan"))
38
67
  else:
39
68
  console.print(Panel.fit(Text(message, style="bold green"), title="Message", border_style="cyan"))
40
- console.print(Panel.fit(Text(response, style="bold white"), title="Response", border_style="cyan"))
41
-
42
- def display_self_reflection(message: str):
43
- console = Console()
69
+ console.print(Panel.fit(Text(response, style="bold blue"), title="Response", border_style="cyan"))
70
+
71
+ def display_self_reflection(message: str, console=None):
72
+ if not message or not message.strip():
73
+ return
74
+ if console is None:
75
+ console = Console()
76
+ message = _clean_display_content(str(message))
44
77
  console.print(Panel.fit(Text(message, style="bold yellow"), title="Self Reflection", border_style="magenta"))
45
78
 
46
- def display_instruction(message: str):
47
- console = Console()
79
+ def display_instruction(message: str, console=None):
80
+ if not message or not message.strip():
81
+ return
82
+ if console is None:
83
+ console = Console()
84
+ message = _clean_display_content(str(message))
48
85
  console.print(Panel.fit(Text(message, style="bold blue"), title="Instruction", border_style="cyan"))
49
86
 
50
- def display_tool_call(message: str):
51
- console = Console()
87
+ def display_tool_call(message: str, console=None):
88
+ if not message or not message.strip():
89
+ return
90
+ if console is None:
91
+ console = Console()
92
+ message = _clean_display_content(str(message))
52
93
  console.print(Panel.fit(Text(message, style="bold cyan"), title="Tool Call", border_style="green"))
53
94
 
54
- def display_error(message: str):
55
- console = Console()
95
+ def display_error(message: str, console=None):
96
+ if not message or not message.strip():
97
+ return
98
+ if console is None:
99
+ console = Console()
100
+ message = _clean_display_content(str(message))
56
101
  console.print(Panel.fit(Text(message, style="bold red"), title="Error", border_style="red"))
57
102
  # Store errors
58
103
  error_logs.append(message)
59
104
 
60
105
  def display_generating(content: str = "", start_time: Optional[float] = None):
106
+ if not content or not str(content).strip():
107
+ return Panel("", title="", border_style="green") # Return empty panel when no content
61
108
  elapsed_str = ""
62
109
  if start_time is not None:
63
110
  elapsed = time.time() - start_time
64
111
  elapsed_str = f" {elapsed:.1f}s"
112
+
113
+ content = _clean_display_content(str(content))
65
114
  return Panel(Markdown(content), title=f"Generating...{elapsed_str}", border_style="green")
66
115
 
67
116
  def clean_triple_backticks(text: str) -> str:
@@ -22,7 +22,8 @@ class Task:
22
22
  status: str = "not started",
23
23
  result: Optional[TaskOutput] = None,
24
24
  create_directory: Optional[bool] = False,
25
- id: Optional[int] = None
25
+ id: Optional[int] = None,
26
+ images: Optional[List[str]] = None
26
27
  ):
27
28
  self.description = description
28
29
  self.expected_output = expected_output
@@ -40,6 +41,7 @@ class Task:
40
41
  self.result = result
41
42
  self.create_directory = create_directory
42
43
  self.id = id
44
+ self.images = images if images else []
43
45
 
44
46
  if self.output_json and self.output_pydantic:
45
47
  raise ValueError("Only one output type can be defined")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: praisonaiagents
3
- Version: 0.0.11
3
+ Version: 0.0.13
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Dist: pydantic
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "praisonaiagents"
7
- version = "0.0.11"
7
+ version = "0.0.13"
8
8
  description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
9
9
  authors = [
10
10
  { name="Mervin Praison" }