praisonaiagents 0.0.12__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.12 → praisonaiagents-0.0.13}/PKG-INFO +1 -1
  2. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/agent/agent.py +40 -16
  3. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/agents/agents.py +81 -1
  4. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/main.py +64 -15
  5. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/task/task.py +3 -1
  6. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents.egg-info/PKG-INFO +1 -1
  7. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/pyproject.toml +1 -1
  8. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/__init__.py +0 -0
  9. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/agent/__init__.py +0 -0
  10. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/agents/__init__.py +0 -0
  11. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/__init__.py +0 -0
  12. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/agent/__init__.py +0 -0
  13. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/agent/agent.py +0 -0
  14. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/agents/__init__.py +0 -0
  15. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/agents/agents.py +0 -0
  16. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/main.py +0 -0
  17. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/task/__init__.py +0 -0
  18. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/build/lib/praisonaiagents/task/task.py +0 -0
  19. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents/task/__init__.py +0 -0
  20. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents.egg-info/SOURCES.txt +0 -0
  21. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents.egg-info/dependency_links.txt +0 -0
  22. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents.egg-info/requires.txt +0 -0
  23. {praisonaiagents-0.0.12 → praisonaiagents-0.0.13}/praisonaiagents.egg-info/top_level.txt +0 -0
  24. {praisonaiagents-0.0.12 → 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.12
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,6 +191,8 @@ 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.
@@ -235,7 +237,6 @@ class Agent:
235
237
  return f"Agent(name='{self.name}', role='{self.role}', goal='{self.goal}')"
236
238
 
237
239
  def _chat_completion(self, messages, temperature=0.2, tools=None, stream=True):
238
- console = Console()
239
240
  start_time = time.time()
240
241
  logging.debug(f"{self.name} sending messages to LLM: {messages}")
241
242
 
@@ -305,12 +306,24 @@ class Agent:
305
306
  stream=True
306
307
  )
307
308
  full_response_text = ""
308
- 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:
309
319
  for chunk in response_stream:
310
320
  if chunk.choices[0].delta.content:
311
321
  full_response_text += chunk.choices[0].delta.content
312
322
  live.update(display_generating(full_response_text, start_time))
313
-
323
+
324
+ # Clear the last generating display with a blank line
325
+ self.console.print()
326
+
314
327
  final_response = client.chat.completions.create(
315
328
  model=self.llm,
316
329
  messages=messages,
@@ -347,7 +360,11 @@ Your Goal: {self.goal}
347
360
  if system_prompt:
348
361
  messages.append({"role": "system", "content": system_prompt})
349
362
  messages.extend(self.chat_history)
350
- 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})
351
368
 
352
369
  final_response_text = None
353
370
  reflection_count = 0
@@ -356,7 +373,14 @@ Your Goal: {self.goal}
356
373
  while True:
357
374
  try:
358
375
  if self.verbose:
359
- 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)
360
384
 
361
385
  response = self._chat_completion(messages, temperature=temperature, tools=tools if tools else None)
362
386
  if not response:
@@ -376,13 +400,13 @@ Your Goal: {self.goal}
376
400
  arguments = json.loads(tool_call.function.arguments)
377
401
 
378
402
  if self.verbose:
379
- 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)
380
404
 
381
405
  tool_result = self.execute_tool(function_name, arguments)
382
406
 
383
407
  if tool_result:
384
408
  if self.verbose:
385
- display_tool_call(f"Function '{function_name}' returned: {tool_result}")
409
+ display_tool_call(f"Function '{function_name}' returned: {tool_result}", console=self.console)
386
410
  messages.append({
387
411
  "role": "tool",
388
412
  "tool_call_id": tool_call.id,
@@ -407,7 +431,7 @@ Your Goal: {self.goal}
407
431
  self.chat_history.append({"role": "assistant", "content": response_text})
408
432
  if self.verbose:
409
433
  logging.info(f"Agent {self.name} final response: {response_text}")
410
- 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)
411
435
  return response_text
412
436
 
413
437
  reflection_prompt = f"""
@@ -430,26 +454,26 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
430
454
  reflection_output = reflection_response.choices[0].message.parsed
431
455
 
432
456
  if self.verbose:
433
- 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)
434
458
 
435
459
  messages.append({"role": "assistant", "content": f"Self Reflection: {reflection_output.reflection} Satisfactory?: {reflection_output.satisfactory}"})
436
460
 
437
461
  # Only consider satisfactory after minimum reflections
438
462
  if reflection_output.satisfactory == "yes" and reflection_count >= self.min_reflect - 1:
439
463
  if self.verbose:
440
- 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)
441
465
  self.chat_history.append({"role": "user", "content": prompt})
442
466
  self.chat_history.append({"role": "assistant", "content": response_text})
443
- 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)
444
468
  return response_text
445
469
 
446
470
  # Check if we've hit max reflections
447
471
  if reflection_count >= self.max_reflect - 1:
448
472
  if self.verbose:
449
- display_self_reflection("Maximum reflection count reached, returning current response")
473
+ display_self_reflection("Maximum reflection count reached, returning current response", console=self.console)
450
474
  self.chat_history.append({"role": "user", "content": prompt})
451
475
  self.chat_history.append({"role": "assistant", "content": response_text})
452
- 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)
453
477
  return response_text
454
478
 
455
479
  logging.debug(f"{self.name} reflection count {reflection_count + 1}, continuing reflection process")
@@ -460,12 +484,12 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
460
484
  continue # Continue the loop for more reflections
461
485
 
462
486
  except Exception as e:
463
- 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)
464
488
  logging.error("Reflection parsing failed.", exc_info=True)
465
489
  messages.append({"role": "assistant", "content": f"Self Reflection failed."})
466
490
  reflection_count += 1
467
491
  continue # Continue even after error to try again
468
492
 
469
493
  except Exception as e:
470
- display_error(f"Error in chat: {e}")
494
+ display_error(f"Error in chat: {e}", console=self.console)
471
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.12
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.12"
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" }