praisonaiagents 0.0.11__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.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" }