praisonaiagents 0.0.12__tar.gz → 0.0.13__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
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" }