praisonaiagents 0.0.12__py3-none-any.whl → 0.0.14__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,
@@ -340,14 +353,33 @@ class Agent:
340
353
  Your Role: {self.role}\n
341
354
  Your Goal: {self.goal}
342
355
  """
356
+ if output_json:
357
+ system_prompt += f"\nReturn ONLY a JSON object that matches this Pydantic model: {output_json.schema_json()}"
343
358
  else:
344
359
  system_prompt = None
345
-
360
+
346
361
  messages = []
347
362
  if system_prompt:
348
363
  messages.append({"role": "system", "content": system_prompt})
349
364
  messages.extend(self.chat_history)
350
- messages.append({"role": "user", "content": prompt})
365
+
366
+ # Modify prompt if output_json is specified
367
+ original_prompt = prompt
368
+ if output_json:
369
+ if isinstance(prompt, str):
370
+ prompt += "\nReturn ONLY a valid JSON object. No other text or explanation."
371
+ elif isinstance(prompt, list):
372
+ # For multimodal prompts, append to the text content
373
+ for item in prompt:
374
+ if item["type"] == "text":
375
+ item["text"] += "\nReturn ONLY a valid JSON object. No other text or explanation."
376
+ break
377
+
378
+ if isinstance(prompt, list):
379
+ # If we receive a multimodal prompt list, place it directly in the user message
380
+ messages.append({"role": "user", "content": prompt})
381
+ else:
382
+ messages.append({"role": "user", "content": prompt})
351
383
 
352
384
  final_response_text = None
353
385
  reflection_count = 0
@@ -356,18 +388,26 @@ Your Goal: {self.goal}
356
388
  while True:
357
389
  try:
358
390
  if self.verbose:
359
- display_instruction(f"Agent {self.name} is processing prompt: {prompt}")
391
+ # Handle both string and list prompts for instruction display
392
+ display_text = prompt
393
+ if isinstance(prompt, list):
394
+ # Extract text content from multimodal prompt
395
+ display_text = next((item["text"] for item in prompt if item["type"] == "text"), "")
396
+
397
+ if display_text and str(display_text).strip():
398
+ display_instruction(f"Agent {self.name} is processing prompt: {display_text}", console=self.console)
360
399
 
361
400
  response = self._chat_completion(messages, temperature=temperature, tools=tools if tools else None)
362
401
  if not response:
363
402
  return None
364
-
403
+
365
404
  tool_calls = getattr(response.choices[0].message, 'tool_calls', None)
405
+ response_text = response.choices[0].message.content.strip()
366
406
 
367
407
  if tool_calls:
368
408
  messages.append({
369
409
  "role": "assistant",
370
- "content": response.choices[0].message.content,
410
+ "content": response_text,
371
411
  "tool_calls": tool_calls
372
412
  })
373
413
 
@@ -376,13 +416,13 @@ Your Goal: {self.goal}
376
416
  arguments = json.loads(tool_call.function.arguments)
377
417
 
378
418
  if self.verbose:
379
- display_tool_call(f"Agent {self.name} is calling function '{function_name}' with arguments: {arguments}")
419
+ display_tool_call(f"Agent {self.name} is calling function '{function_name}' with arguments: {arguments}", console=self.console)
380
420
 
381
421
  tool_result = self.execute_tool(function_name, arguments)
382
422
 
383
423
  if tool_result:
384
424
  if self.verbose:
385
- display_tool_call(f"Function '{function_name}' returned: {tool_result}")
425
+ display_tool_call(f"Function '{function_name}' returned: {tool_result}", console=self.console)
386
426
  messages.append({
387
427
  "role": "tool",
388
428
  "tool_call_id": tool_call.id,
@@ -399,15 +439,31 @@ Your Goal: {self.goal}
399
439
  if not response:
400
440
  return None
401
441
  response_text = response.choices[0].message.content.strip()
402
- else:
403
- response_text = response.choices[0].message.content.strip()
442
+
443
+ # Handle output_json if specified
444
+ if output_json:
445
+ try:
446
+ # Clean the response text to get only JSON
447
+ cleaned_json = self.clean_json_output(response_text)
448
+ # Parse into Pydantic model
449
+ parsed_model = output_json.model_validate_json(cleaned_json)
450
+ # Add to chat history and return
451
+ self.chat_history.append({"role": "user", "content": original_prompt})
452
+ self.chat_history.append({"role": "assistant", "content": response_text})
453
+ if self.verbose:
454
+ display_interaction(original_prompt, response_text, markdown=self.markdown,
455
+ generation_time=time.time() - start_time, console=self.console)
456
+ return parsed_model
457
+ except Exception as e:
458
+ display_error(f"Failed to parse response as {output_json.__name__}: {e}")
459
+ return None
404
460
 
405
461
  if not self.self_reflect:
406
- self.chat_history.append({"role": "user", "content": prompt})
462
+ self.chat_history.append({"role": "user", "content": original_prompt})
407
463
  self.chat_history.append({"role": "assistant", "content": response_text})
408
464
  if self.verbose:
409
465
  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)
466
+ display_interaction(original_prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time, console=self.console)
411
467
  return response_text
412
468
 
413
469
  reflection_prompt = f"""
@@ -430,26 +486,26 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
430
486
  reflection_output = reflection_response.choices[0].message.parsed
431
487
 
432
488
  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}'")
489
+ 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
490
 
435
491
  messages.append({"role": "assistant", "content": f"Self Reflection: {reflection_output.reflection} Satisfactory?: {reflection_output.satisfactory}"})
436
492
 
437
493
  # Only consider satisfactory after minimum reflections
438
494
  if reflection_output.satisfactory == "yes" and reflection_count >= self.min_reflect - 1:
439
495
  if self.verbose:
440
- display_self_reflection("Agent marked the response as satisfactory after meeting minimum reflections")
496
+ display_self_reflection("Agent marked the response as satisfactory after meeting minimum reflections", console=self.console)
441
497
  self.chat_history.append({"role": "user", "content": prompt})
442
498
  self.chat_history.append({"role": "assistant", "content": response_text})
443
- display_interaction(prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time)
499
+ display_interaction(prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time, console=self.console)
444
500
  return response_text
445
501
 
446
502
  # Check if we've hit max reflections
447
503
  if reflection_count >= self.max_reflect - 1:
448
504
  if self.verbose:
449
- display_self_reflection("Maximum reflection count reached, returning current response")
505
+ display_self_reflection("Maximum reflection count reached, returning current response", console=self.console)
450
506
  self.chat_history.append({"role": "user", "content": prompt})
451
507
  self.chat_history.append({"role": "assistant", "content": response_text})
452
- display_interaction(prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time)
508
+ display_interaction(prompt, response_text, markdown=self.markdown, generation_time=time.time() - start_time, console=self.console)
453
509
  return response_text
454
510
 
455
511
  logging.debug(f"{self.name} reflection count {reflection_count + 1}, continuing reflection process")
@@ -460,12 +516,24 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
460
516
  continue # Continue the loop for more reflections
461
517
 
462
518
  except Exception as e:
463
- display_error(f"Error in parsing self-reflection json {e}. Retrying")
519
+ display_error(f"Error in parsing self-reflection json {e}. Retrying", console=self.console)
464
520
  logging.error("Reflection parsing failed.", exc_info=True)
465
521
  messages.append({"role": "assistant", "content": f"Self Reflection failed."})
466
522
  reflection_count += 1
467
523
  continue # Continue even after error to try again
468
524
 
469
525
  except Exception as e:
470
- display_error(f"Error in chat: {e}")
471
- return None
526
+ display_error(f"Error in chat: {e}", console=self.console)
527
+ return None
528
+
529
+ def clean_json_output(self, output: str) -> str:
530
+ """Clean and extract JSON from response text."""
531
+ cleaned = output.strip()
532
+ # Remove markdown code blocks if present
533
+ if cleaned.startswith("```json"):
534
+ cleaned = cleaned[len("```json"):].strip()
535
+ if cleaned.startswith("```"):
536
+ cleaned = cleaned[len("```"):].strip()
537
+ if cleaned.endswith("```"):
538
+ cleaned = cleaned[:-3].strip()
539
+ return cleaned
@@ -2,7 +2,7 @@ import os
2
2
  import time
3
3
  import json
4
4
  import logging
5
- from typing import Any, Dict, Optional
5
+ from typing import Any, Dict, Optional, List
6
6
  from pydantic import BaseModel
7
7
  from rich.text import Text
8
8
  from rich.panel import Panel
@@ -11,6 +11,36 @@ 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
+ class LoopItems(BaseModel):
15
+ items: List[Any]
16
+
17
+ def encode_file_to_base64(file_path: str) -> str:
18
+ """Base64-encode a file."""
19
+ import base64
20
+ with open(file_path, "rb") as f:
21
+ return base64.b64encode(f.read()).decode("utf-8")
22
+
23
+ def process_video(video_path: str, seconds_per_frame=2):
24
+ """Split video into frames (base64-encoded)."""
25
+ import cv2
26
+ import base64
27
+ base64_frames = []
28
+ video = cv2.VideoCapture(video_path)
29
+ total_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
30
+ fps = video.get(cv2.CAP_PROP_FPS)
31
+ frames_to_skip = int(fps * seconds_per_frame)
32
+ curr_frame = 0
33
+ while curr_frame < total_frames:
34
+ video.set(cv2.CAP_PROP_POS_FRAMES, curr_frame)
35
+ success, frame = video.read()
36
+ if not success:
37
+ break
38
+ _, buffer = cv2.imencode(".jpg", frame)
39
+ base64_frames.append(base64.b64encode(buffer).decode("utf-8"))
40
+ curr_frame += frames_to_skip
41
+ video.release()
42
+ return base64_frames
43
+
14
44
  class PraisonAIAgents:
15
45
  def __init__(self, agents, tasks, verbose=0, completion_checker=None, max_retries=5, process="sequential", manager_llm=None):
16
46
  self.agents = agents
@@ -28,6 +58,7 @@ class PraisonAIAgents:
28
58
  for task in tasks:
29
59
  self.add_task(task)
30
60
  task.status = "not started"
61
+ self._state = {} # Add state storage at PraisonAIAgents level
31
62
 
32
63
  def add_task(self, task):
33
64
  task_id = self.task_id_counter
@@ -58,6 +89,19 @@ class PraisonAIAgents:
58
89
  display_error(f"Error: Task with ID {task_id} does not exist")
59
90
  return
60
91
  task = self.tasks[task_id]
92
+
93
+ # Only import multimodal dependencies if task has images
94
+ if task.images and task.status == "not started":
95
+ try:
96
+ import cv2
97
+ import base64
98
+ from moviepy import VideoFileClip
99
+ except ImportError as e:
100
+ display_error(f"Error: Missing required dependencies for image/video processing: {e}")
101
+ display_error("Please install with: pip install opencv-python moviepy")
102
+ task.status = "failed"
103
+ return None
104
+
61
105
  if task.status == "not started":
62
106
  task.status = "in progress"
63
107
 
@@ -83,7 +127,47 @@ Expected Output: {task.expected_output}.
83
127
  if self.verbose >= 2:
84
128
  logging.info(f"Executing task {task_id}: {task.description} using {executor_agent.name}")
85
129
  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)
130
+
131
+ if task.images:
132
+ def _get_multimodal_message(text_prompt, images):
133
+ content = [{"type": "text", "text": text_prompt}]
134
+
135
+ for img in images:
136
+ # If local file path for a valid image
137
+ if os.path.exists(img):
138
+ ext = os.path.splitext(img)[1].lower()
139
+ # If it's a .mp4, convert to frames
140
+ if ext == ".mp4":
141
+ frames = process_video(img, seconds_per_frame=1)
142
+ content.append({"type": "text", "text": "These are frames from the video."})
143
+ for f in frames:
144
+ content.append({
145
+ "type": "image_url",
146
+ "image_url": {"url": f"data:image/jpg;base64,{f}"}
147
+ })
148
+ else:
149
+ encoded = encode_file_to_base64(img)
150
+ content.append({
151
+ "type": "image_url",
152
+ "image_url": {
153
+ "url": f"data:image/{ext.lstrip('.')};base64,{encoded}"
154
+ }
155
+ })
156
+ else:
157
+ # Treat as a remote URL
158
+ content.append({
159
+ "type": "image_url",
160
+ "image_url": {"url": img}
161
+ })
162
+ return content
163
+
164
+ agent_output = executor_agent.chat(
165
+ _get_multimodal_message(task_prompt, task.images),
166
+ tools=task.tools
167
+ )
168
+ else:
169
+ agent_output = executor_agent.chat(task_prompt, tools=task.tools)
170
+
87
171
  if agent_output:
88
172
  task_output = TaskOutput(
89
173
  description=task.description,
@@ -171,11 +255,144 @@ Expected Output: {task.expected_output}.
171
255
  logging.info(f"Task {task_id} failed after {self.max_retries} retries.")
172
256
 
173
257
  def run_all_tasks(self):
174
- if self.process == "sequential":
258
+ """Execute tasks based on execution mode"""
259
+ if self.process == "workflow":
260
+ # Build workflow relationships first
261
+ for task in self.tasks.values():
262
+ if task.next_tasks:
263
+ for next_task_name in task.next_tasks:
264
+ next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
265
+ if next_task:
266
+ next_task.previous_tasks.append(task.name)
267
+
268
+ # Find start task
269
+ start_task = None
270
+ for task_id, task in self.tasks.items():
271
+ if task.is_start:
272
+ start_task = task
273
+ break
274
+
275
+ if not start_task:
276
+ start_task = list(self.tasks.values())[0]
277
+ logging.info("No start task marked, using first task")
278
+
279
+ current_task = start_task
280
+ visited_tasks = set()
281
+ loop_data = {} # Store loop-specific data
282
+
283
+ while current_task and current_task.id not in visited_tasks:
284
+ task_id = current_task.id
285
+ logging.info(f"Executing workflow task: {current_task.name if current_task.name else task_id}")
286
+
287
+ # Add context from previous tasks to description
288
+ if current_task.previous_tasks or current_task.context:
289
+ context = "\nInput data from previous tasks:"
290
+
291
+ # Add data from previous tasks in workflow
292
+ for prev_name in current_task.previous_tasks:
293
+ prev_task = next((t for t in self.tasks.values() if t.name == prev_name), None)
294
+ if prev_task and prev_task.result:
295
+ # Handle loop data
296
+ if current_task.task_type == "loop":
297
+ # create a loop manager Agent
298
+ loop_manager = Agent(
299
+ name="Loop Manager",
300
+ role="Loop data processor",
301
+ goal="Process loop data and convert it to list format",
302
+ backstory="Expert at handling loop data and converting it to proper format",
303
+ llm=self.manager_llm,
304
+ verbose=self.verbose,
305
+ markdown=True
306
+ )
307
+
308
+ # get the loop data convert it to list using calling Agent class chat
309
+ loop_prompt = f"""
310
+ Process this data into a list format:
311
+ {prev_task.result.raw}
312
+
313
+ Return a JSON object with an 'items' array containing the items to process.
314
+ """
315
+ loop_data_str = loop_manager.chat(
316
+ prompt=loop_prompt,
317
+ output_json=LoopItems
318
+ )
319
+
320
+ try:
321
+ # The response will already be parsed into LoopItems model
322
+ loop_data[f"loop_{current_task.name}"] = {
323
+ "items": loop_data_str.items,
324
+ "index": 0,
325
+ "remaining": len(loop_data_str.items)
326
+ }
327
+ context += f"\nCurrent loop item: {loop_data_str.items[0]}"
328
+ except Exception as e:
329
+ display_error(f"Failed to process loop data: {e}")
330
+ context += f"\n{prev_name}: {prev_task.result.raw}"
331
+ else:
332
+ context += f"\n{prev_name}: {prev_task.result.raw}"
333
+
334
+ # Add data from context tasks
335
+ if current_task.context:
336
+ for ctx_task in current_task.context:
337
+ if ctx_task.result and ctx_task.name != current_task.name:
338
+ context += f"\n{ctx_task.name}: {ctx_task.result.raw}"
339
+
340
+ # Update task description with context
341
+ current_task.description = current_task.description + context
342
+
343
+ # Execute task using existing run_task method
344
+ self.run_task(task_id)
345
+ visited_tasks.add(task_id)
346
+
347
+ # Handle loop progression
348
+ if current_task.task_type == "loop":
349
+ loop_key = f"loop_{current_task.name}"
350
+ if loop_key in loop_data:
351
+ loop_info = loop_data[loop_key]
352
+ loop_info["index"] += 1
353
+ has_more = loop_info["remaining"] > 0
354
+
355
+ # Update result to trigger correct condition
356
+ if current_task.result:
357
+ result = current_task.result.raw
358
+ if has_more:
359
+ result += "\nmore"
360
+ else:
361
+ result += "\ndone"
362
+ current_task.result.raw = result
363
+
364
+ # Determine next task based on result
365
+ next_task = None
366
+ if current_task.result:
367
+ if current_task.task_type in ["decision", "loop"]:
368
+ result = current_task.result.raw.lower()
369
+ # Check conditions
370
+ for condition, tasks in current_task.condition.items():
371
+ if condition.lower() in result and tasks:
372
+ next_task_name = tasks[0]
373
+ next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
374
+ # For loops, allow revisiting the same task
375
+ if next_task and next_task.id == current_task.id:
376
+ visited_tasks.discard(current_task.id)
377
+ break
378
+
379
+ if not next_task and current_task.next_tasks:
380
+ next_task_name = current_task.next_tasks[0]
381
+ next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
382
+
383
+ current_task = next_task
384
+ if not current_task:
385
+ logging.info("Workflow execution completed")
386
+ break
387
+
388
+ elif self.process == "sequential":
389
+ # Keep original sequential execution
175
390
  for task_id in self.tasks:
176
391
  if self.tasks[task_id].status != "completed":
177
392
  self.run_task(task_id)
393
+
178
394
  elif self.process == "hierarchical":
395
+ # Keep original hierarchical execution
179
396
  logging.debug(f"Starting hierarchical task execution with {len(self.tasks)} tasks")
180
397
  manager_agent = Agent(
181
398
  name="Manager",
@@ -317,4 +534,20 @@ Provide a JSON with the structure:
317
534
  return {
318
535
  "task_status": self.get_all_tasks_status(),
319
536
  "task_results": {task_id: self.get_task_result(task_id) for task_id in self.tasks}
320
- }
537
+ }
538
+
539
+ def set_state(self, key: str, value: Any) -> None:
540
+ """Set a state value"""
541
+ self._state[key] = value
542
+
543
+ def get_state(self, key: str, default: Any = None) -> Any:
544
+ """Get a state value"""
545
+ return self._state.get(key, default)
546
+
547
+ def update_state(self, updates: Dict) -> None:
548
+ """Update multiple state values"""
549
+ self._state.update(updates)
550
+
551
+ def clear_state(self) -> None:
552
+ """Clear all state values"""
553
+ self._state.clear()
praisonaiagents/main.py CHANGED
@@ -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:
@@ -1,8 +1,9 @@
1
1
  import logging
2
- from typing import List, Optional, Dict, Any, Type
2
+ from typing import List, Optional, Dict, Any, Type, Callable, Union
3
3
  from pydantic import BaseModel
4
4
  from ..main import TaskOutput
5
5
  from ..agent.agent import Agent
6
+ import uuid
6
7
 
7
8
  class Task:
8
9
  def __init__(
@@ -22,11 +23,18 @@ class Task:
22
23
  status: str = "not started",
23
24
  result: Optional[TaskOutput] = None,
24
25
  create_directory: Optional[bool] = False,
25
- id: Optional[int] = None
26
+ id: Optional[int] = None,
27
+ images: Optional[List[str]] = None,
28
+ next_tasks: Optional[List[str]] = None,
29
+ task_type: str = "task",
30
+ condition: Optional[Dict[str, List[str]]] = None,
31
+ is_start: bool = False,
32
+ loop_state: Optional[Dict[str, Union[str, int]]] = None
26
33
  ):
34
+ self.id = str(uuid.uuid4()) if id is None else str(id)
35
+ self.name = name
27
36
  self.description = description
28
37
  self.expected_output = expected_output
29
- self.name = name
30
38
  self.agent = agent
31
39
  self.tools = tools if tools else []
32
40
  self.context = context if context else []
@@ -39,10 +47,18 @@ class Task:
39
47
  self.status = status
40
48
  self.result = result
41
49
  self.create_directory = create_directory
42
- self.id = id
50
+ self.images = images if images else []
51
+ self.next_tasks = next_tasks if next_tasks else []
52
+ self.task_type = task_type
53
+ self.condition = condition if condition else {}
54
+ self.is_start = is_start
55
+ self.loop_state = loop_state if loop_state else {}
43
56
 
44
57
  if self.output_json and self.output_pydantic:
45
58
  raise ValueError("Only one output type can be defined")
46
59
 
60
+ # Track previous tasks based on next_tasks relationships
61
+ self.previous_tasks = []
62
+
47
63
  def __str__(self):
48
64
  return f"Task(name='{self.name if self.name else 'None'}', description='{self.description}', agent='{self.agent.name if self.agent else 'None'}', status='{self.status}')"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: praisonaiagents
3
- Version: 0.0.12
3
+ Version: 0.0.14
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Dist: pydantic
@@ -1,9 +1,9 @@
1
1
  praisonaiagents/__init__.py,sha256=gI8vEabBTRPsE_E8GA5sBMi4sTtJI-YokPrH2Nor-k0,741
2
- praisonaiagents/main.py,sha256=zDhN5KKtKbfruolDNxlyJkcFlkSt4KQkQTDRfQVAhxc,3960
2
+ praisonaiagents/main.py,sha256=K2OxVKPmo4dNJbSWIsXDi_hm9CRx5O4km_74UGcszhk,5744
3
3
  praisonaiagents/agent/__init__.py,sha256=sKO8wGEXvtCrvV1e834r1Okv0XAqAxqZCqz6hKLiTvA,79
4
- praisonaiagents/agent/agent.py,sha256=hf9z2XwdUF5n_A1ztDM9qGvaKJj40SqJm9WblwLcKjI,20328
4
+ praisonaiagents/agent/agent.py,sha256=zTYcDpJ5DzzBnefwLvhrtBlGQoRI4ZZAioDu5nKTPSs,24042
5
5
  praisonaiagents/agents/__init__.py,sha256=7RDeQNSqZg5uBjD4M_0p_F6YgfWuDuxPFydPU50kDYc,120
6
- praisonaiagents/agents/agents.py,sha256=CBN7OQwFbTgiKXVn7o8987mBH9TUr5s721AC-FSu8AQ,13680
6
+ praisonaiagents/agents/agents.py,sha256=ITvH8Yq_OzhyMC_Aid4qlqQbEM9cCfp7SayXg0ASJ5k,24526
7
7
  praisonaiagents/build/lib/praisonaiagents/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
8
8
  praisonaiagents/build/lib/praisonaiagents/main.py,sha256=zDhN5KKtKbfruolDNxlyJkcFlkSt4KQkQTDRfQVAhxc,3960
9
9
  praisonaiagents/build/lib/praisonaiagents/agent/__init__.py,sha256=sKO8wGEXvtCrvV1e834r1Okv0XAqAxqZCqz6hKLiTvA,79
@@ -13,8 +13,8 @@ praisonaiagents/build/lib/praisonaiagents/agents/agents.py,sha256=P2FAtlfD3kPib5
13
13
  praisonaiagents/build/lib/praisonaiagents/task/__init__.py,sha256=VL5hXVmyGjINb34AalxpBMl-YW9m5EDcRkMTKkSSl7c,80
14
14
  praisonaiagents/build/lib/praisonaiagents/task/task.py,sha256=4Y1qX8OeEFcid2yhAiPYylvHpuDmWORsyNL16_BiVvI,1831
15
15
  praisonaiagents/task/__init__.py,sha256=VL5hXVmyGjINb34AalxpBMl-YW9m5EDcRkMTKkSSl7c,80
16
- praisonaiagents/task/task.py,sha256=4Y1qX8OeEFcid2yhAiPYylvHpuDmWORsyNL16_BiVvI,1831
17
- praisonaiagents-0.0.12.dist-info/METADATA,sha256=ci_5OYkXaI9I_O1sMV3-P-i-Gi9jX9S_GzDXCNrnV8Y,233
18
- praisonaiagents-0.0.12.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
19
- praisonaiagents-0.0.12.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
20
- praisonaiagents-0.0.12.dist-info/RECORD,,
16
+ praisonaiagents/task/task.py,sha256=oMC5Zz1dMj0Ceice69aBS1KQQXMLqphc8wNOQ9zcu0Q,2570
17
+ praisonaiagents-0.0.14.dist-info/METADATA,sha256=-pdlX7m7Sr2IovIrRt9QyBfkiwgK81rEd3_VklcmHNs,233
18
+ praisonaiagents-0.0.14.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
19
+ praisonaiagents-0.0.14.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
20
+ praisonaiagents-0.0.14.dist-info/RECORD,,