praisonaiagents 0.0.42__py3-none-any.whl → 0.0.44__py3-none-any.whl

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.
@@ -45,12 +45,13 @@ def process_video(video_path: str, seconds_per_frame=2):
45
45
  return base64_frames
46
46
 
47
47
  class PraisonAIAgents:
48
- def __init__(self, agents, tasks=None, verbose=0, completion_checker=None, max_retries=5, process="sequential", manager_llm=None, memory=False, memory_config=None, embedder=None, user_id=None):
48
+ def __init__(self, agents, tasks=None, verbose=0, completion_checker=None, max_retries=5, process="sequential", manager_llm=None, memory=False, memory_config=None, embedder=None, user_id=None, max_iter=10):
49
49
  if not agents:
50
50
  raise ValueError("At least one agent must be provided")
51
51
 
52
52
  self.run_id = str(uuid.uuid4()) # Auto-generate run_id
53
53
  self.user_id = user_id # Optional user_id
54
+ self.max_iter = max_iter # Add max_iter parameter
54
55
 
55
56
  # Pass user_id to each agent
56
57
  for agent in agents:
@@ -402,7 +403,8 @@ Context:
402
403
  tasks=self.tasks,
403
404
  agents=self.agents,
404
405
  manager_llm=self.manager_llm,
405
- verbose=self.verbose
406
+ verbose=self.verbose,
407
+ max_iter=self.max_iter
406
408
  )
407
409
 
408
410
  if self.process == "workflow":
@@ -710,7 +712,8 @@ Context:
710
712
  tasks=self.tasks,
711
713
  agents=self.agents,
712
714
  manager_llm=self.manager_llm,
713
- verbose=self.verbose
715
+ verbose=self.verbose,
716
+ max_iter=self.max_iter
714
717
  )
715
718
 
716
719
  if self.process == "workflow":
praisonaiagents/main.py CHANGED
@@ -23,6 +23,10 @@ logging.basicConfig(
23
23
  handlers=[RichHandler(rich_tracebacks=True)]
24
24
  )
25
25
 
26
+ # Add these lines to suppress markdown parser debug logs
27
+ logging.getLogger('markdown_it').setLevel(logging.WARNING)
28
+ logging.getLogger('rich.markdown').setLevel(logging.WARNING)
29
+
26
30
  # Global list to store error logs
27
31
  error_logs = []
28
32
 
@@ -5,6 +5,8 @@ from pydantic import BaseModel
5
5
  from ..agent.agent import Agent
6
6
  from ..task.task import Task
7
7
  from ..main import display_error, client
8
+ import csv
9
+ import os
8
10
 
9
11
  class LoopItems(BaseModel):
10
12
  items: List[Any]
@@ -62,46 +64,47 @@ class Process:
62
64
  if prev_task and prev_task.result:
63
65
  # Handle loop data
64
66
  if current_task.task_type == "loop":
65
- # create a loop manager Agent
66
- loop_manager = Agent(
67
- name="Loop Manager",
68
- role="Loop data processor",
69
- goal="Process loop data and convert it to list format",
70
- backstory="Expert at handling loop data and converting it to proper format",
71
- llm=self.manager_llm,
72
- verbose=self.verbose,
73
- markdown=True
74
- )
75
-
76
- # get the loop data convert it to list using calling Agent class chat
77
- loop_prompt = f"""
78
- Process this data into a list format:
79
- {prev_task.result.raw}
80
-
81
- Return a JSON object with an 'items' array containing the items to process.
82
- """
83
- if current_task.async_execution:
84
- loop_data_str = await loop_manager.achat(
85
- prompt=loop_prompt,
86
- output_json=LoopItems
87
- )
88
- else:
89
- loop_data_str = loop_manager.chat(
90
- prompt=loop_prompt,
91
- output_json=LoopItems
92
- )
67
+ # # create a loop manager Agent
68
+ # loop_manager = Agent(
69
+ # name="Loop Manager",
70
+ # role="Loop data processor",
71
+ # goal="Process loop data and convert it to list format",
72
+ # backstory="Expert at handling loop data and converting it to proper format",
73
+ # llm=self.manager_llm,
74
+ # verbose=self.verbose,
75
+ # markdown=True
76
+ # )
77
+
78
+ # # get the loop data convert it to list using calling Agent class chat
79
+ # loop_prompt = f"""
80
+ # Process this data into a list format:
81
+ # {prev_task.result.raw}
82
+
83
+ # Return a JSON object with an 'items' array containing the items to process.
84
+ # """
85
+ # if current_task.async_execution:
86
+ # loop_data_str = await loop_manager.achat(
87
+ # prompt=loop_prompt,
88
+ # output_json=LoopItems
89
+ # )
90
+ # else:
91
+ # loop_data_str = loop_manager.chat(
92
+ # prompt=loop_prompt,
93
+ # output_json=LoopItems
94
+ # )
93
95
 
94
- try:
95
- # The response will already be parsed into LoopItems model
96
- loop_data[f"loop_{current_task.name}"] = {
97
- "items": loop_data_str.items,
98
- "index": 0,
99
- "remaining": len(loop_data_str.items)
100
- }
101
- context += f"\nCurrent loop item: {loop_data_str.items[0]}"
102
- except Exception as e:
103
- display_error(f"Failed to process loop data: {e}")
104
- context += f"\n{prev_name}: {prev_task.result.raw}"
96
+ # try:
97
+ # # The response will already be parsed into LoopItems model
98
+ # loop_data[f"loop_{current_task.name}"] = {
99
+ # "items": loop_data_str.items,
100
+ # "index": 0,
101
+ # "remaining": len(loop_data_str.items)
102
+ # }
103
+ # context += f"\nCurrent loop item: {loop_data_str.items[0]}"
104
+ # except Exception as e:
105
+ # display_error(f"Failed to process loop data: {e}")
106
+ # context += f"\n{prev_name}: {prev_task.result.raw}"
107
+ context += f"\n{prev_name}: {prev_task.result.raw}"
105
108
  else:
106
109
  context += f"\n{prev_name}: {prev_task.result.raw}"
107
110
 
@@ -144,13 +147,17 @@ Return a JSON object with an 'items' array containing the items to process.
144
147
  next_task = None
145
148
  if current_task and current_task.result:
146
149
  if current_task.task_type in ["decision", "loop"]:
147
- result = current_task.result.raw.lower()
150
+ # MINIMAL CHANGE: use pydantic decision if present
151
+ decision_str = current_task.result.raw.lower()
152
+ if current_task.result.pydantic and hasattr(current_task.result.pydantic, "decision"):
153
+ decision_str = current_task.result.pydantic.decision.lower()
154
+
148
155
  # Check conditions
149
156
  for condition, tasks in current_task.condition.items():
150
- if condition.lower() in result:
157
+ if condition.lower() == decision_str:
151
158
  # Handle both list and direct string values
152
159
  task_value = tasks[0] if isinstance(tasks, list) else tasks
153
- if not task_value or task_value == "exit": # If empty or explicit exit
160
+ if not task_value or task_value == "exit":
154
161
  logging.info("Workflow exit condition met, ending workflow")
155
162
  current_task = None
156
163
  break
@@ -322,7 +329,76 @@ Provide a JSON with the structure:
322
329
  if not start_task:
323
330
  start_task = list(self.tasks.values())[0]
324
331
  logging.info("No start task marked, using first task")
325
-
332
+
333
+ # If loop type and no input_file, default to tasks.csv
334
+ if start_task and start_task.task_type == "loop" and not start_task.input_file:
335
+ start_task.input_file = "tasks.csv"
336
+
337
+ # --- If loop + input_file, read file & create tasks
338
+ if start_task and start_task.task_type == "loop" and getattr(start_task, "input_file", None):
339
+ try:
340
+ file_ext = os.path.splitext(start_task.input_file)[1].lower()
341
+ new_tasks = []
342
+
343
+ if file_ext == ".csv":
344
+ # existing CSV reading logic
345
+ with open(start_task.input_file, "r", encoding="utf-8") as f:
346
+ # Try as simple CSV first
347
+ reader = csv.reader(f)
348
+ previous_task = None
349
+ for i, row in enumerate(reader):
350
+ if row: # Skip empty rows
351
+ task_desc = row[0] # Take first column
352
+ row_task = Task(
353
+ description=task_desc, # Keep full row as description
354
+ agent=start_task.agent,
355
+ name=task_desc, # Use first column as name
356
+ is_start=(i == 0),
357
+ task_type="task",
358
+ condition={
359
+ "complete": ["next"],
360
+ "retry": ["current"]
361
+ }
362
+ )
363
+ self.tasks[row_task.id] = row_task
364
+ new_tasks.append(row_task)
365
+
366
+ if previous_task:
367
+ previous_task.next_tasks = [row_task.name]
368
+ previous_task.condition["complete"] = [row_task.name]
369
+ previous_task = row_task
370
+ else:
371
+ # If not CSV, read lines
372
+ with open(start_task.input_file, "r", encoding="utf-8") as f:
373
+ lines = f.read().splitlines()
374
+ previous_task = None
375
+ for i, line in enumerate(lines):
376
+ row_task = Task(
377
+ description=line.strip(),
378
+ agent=start_task.agent,
379
+ name=line.strip(),
380
+ is_start=(i == 0),
381
+ task_type="task",
382
+ condition={
383
+ "complete": ["next"],
384
+ "retry": ["current"]
385
+ }
386
+ )
387
+ self.tasks[row_task.id] = row_task
388
+ new_tasks.append(row_task)
389
+
390
+ if previous_task:
391
+ previous_task.next_tasks = [row_task.name]
392
+ previous_task.condition["complete"] = [row_task.name]
393
+ previous_task = row_task
394
+
395
+ if new_tasks:
396
+ start_task = new_tasks[0]
397
+ logging.info(f"Created {len(new_tasks)} tasks from: {start_task.input_file}")
398
+ except Exception as e:
399
+ logging.error(f"Failed to read file tasks: {e}")
400
+
401
+ # end of the new block
326
402
  current_task = start_task
327
403
  visited_tasks = set()
328
404
  loop_data = {} # Store loop-specific data
@@ -346,40 +422,41 @@ Provide a JSON with the structure:
346
422
  if prev_task and prev_task.result:
347
423
  # Handle loop data
348
424
  if current_task.task_type == "loop":
349
- # create a loop manager Agent
350
- loop_manager = Agent(
351
- name="Loop Manager",
352
- role="Loop data processor",
353
- goal="Process loop data and convert it to list format",
354
- backstory="Expert at handling loop data and converting it to proper format",
355
- llm=self.manager_llm,
356
- verbose=self.verbose,
357
- markdown=True
358
- )
359
-
360
- # get the loop data convert it to list using calling Agent class chat
361
- loop_prompt = f"""
362
- Process this data into a list format:
363
- {prev_task.result.raw}
364
-
365
- Return a JSON object with an 'items' array containing the items to process.
366
- """
367
- loop_data_str = loop_manager.chat(
368
- prompt=loop_prompt,
369
- output_json=LoopItems
370
- )
425
+ # # create a loop manager Agent
426
+ # loop_manager = Agent(
427
+ # name="Loop Manager",
428
+ # role="Loop data processor",
429
+ # goal="Process loop data and convert it to list format",
430
+ # backstory="Expert at handling loop data and converting it to proper format",
431
+ # llm=self.manager_llm,
432
+ # verbose=self.verbose,
433
+ # markdown=True
434
+ # )
435
+
436
+ # # get the loop data convert it to list using calling Agent class chat
437
+ # loop_prompt = f"""
438
+ # Process this data into a list format:
439
+ # {prev_task.result.raw}
440
+
441
+ # Return a JSON object with an 'items' array containing the items to process.
442
+ # """
443
+ # loop_data_str = loop_manager.chat(
444
+ # prompt=loop_prompt,
445
+ # output_json=LoopItems
446
+ # )
371
447
 
372
- try:
373
- # The response will already be parsed into LoopItems model
374
- loop_data[f"loop_{current_task.name}"] = {
375
- "items": loop_data_str.items,
376
- "index": 0,
377
- "remaining": len(loop_data_str.items)
378
- }
379
- context += f"\nCurrent loop item: {loop_data_str.items[0]}"
380
- except Exception as e:
381
- display_error(f"Failed to process loop data: {e}")
382
- context += f"\n{prev_name}: {prev_task.result.raw}"
448
+ # try:
449
+ # # The response will already be parsed into LoopItems model
450
+ # loop_data[f"loop_{current_task.name}"] = {
451
+ # "items": loop_data_str.items,
452
+ # "index": 0,
453
+ # "remaining": len(loop_data_str.items)
454
+ # }
455
+ # context += f"\nCurrent loop item: {loop_data_str.items[0]}"
456
+ # except Exception as e:
457
+ # display_error(f"Failed to process loop data: {e}")
458
+ # context += f"\n{prev_name}: {prev_task.result.raw}"
459
+ context += f"\n{prev_name}: {prev_task.result.raw}"
383
460
  else:
384
461
  context += f"\n{prev_name}: {prev_task.result.raw}"
385
462
 
@@ -396,7 +473,7 @@ Return a JSON object with an 'items' array containing the items to process.
396
473
  yield task_id
397
474
  visited_tasks.add(task_id)
398
475
 
399
- # Reset completed task to "not started" so it can run again
476
+ # Reset completed task to "not started" so it can run again: Only for workflow because some tasks may be revisited
400
477
  if self.tasks[task_id].status == "completed":
401
478
  logging.debug(f"Task {task_id} was completed, resetting to 'not started' for next iteration.")
402
479
  self.tasks[task_id].status = "not started"
@@ -422,13 +499,17 @@ Return a JSON object with an 'items' array containing the items to process.
422
499
  next_task = None
423
500
  if current_task and current_task.result:
424
501
  if current_task.task_type in ["decision", "loop"]:
425
- result = current_task.result.raw.lower()
502
+ # MINIMAL CHANGE: use pydantic decision if present
503
+ decision_str = current_task.result.raw.lower()
504
+ if current_task.result.pydantic and hasattr(current_task.result.pydantic, "decision"):
505
+ decision_str = current_task.result.pydantic.decision.lower()
506
+
426
507
  # Check conditions
427
508
  for condition, tasks in current_task.condition.items():
428
- if condition.lower() in result:
509
+ if condition.lower() == decision_str:
429
510
  # Handle both list and direct string values
430
511
  task_value = tasks[0] if isinstance(tasks, list) else tasks
431
- if not task_value or task_value == "exit": # If empty or explicit exit
512
+ if not task_value or task_value == "exit":
432
513
  logging.info("Workflow exit condition met, ending workflow")
433
514
  current_task = None
434
515
  break
@@ -1,6 +1,6 @@
1
1
  import logging
2
2
  import asyncio
3
- from typing import List, Optional, Dict, Any, Type, Callable, Union, Coroutine
3
+ from typing import List, Optional, Dict, Any, Type, Callable, Union, Coroutine, Literal
4
4
  from pydantic import BaseModel
5
5
  from ..main import TaskOutput
6
6
  from ..agent.agent import Agent
@@ -37,8 +37,10 @@ class Task:
37
37
  is_start: bool = False,
38
38
  loop_state: Optional[Dict[str, Union[str, int]]] = None,
39
39
  memory=None,
40
- quality_check=True
40
+ quality_check=True,
41
+ input_file: Optional[str] = None
41
42
  ):
43
+ self.input_file = input_file
42
44
  self.id = str(uuid.uuid4()) if id is None else str(id)
43
45
  self.name = name
44
46
  self.description = description
@@ -83,6 +85,47 @@ class Task:
83
85
  # Track previous tasks based on next_tasks relationships
84
86
  self.previous_tasks = []
85
87
 
88
+ # If task_type="decision" and output_pydantic is not set
89
+ if self.task_type == "decision" and not self.output_pydantic:
90
+ from pydantic import BaseModel
91
+ from typing import Literal
92
+
93
+ # Gather condition keys for the "decision" field
94
+ condition_keys = list(self.condition.keys())
95
+ if not condition_keys:
96
+ # Fall back to placeholders if nothing is specified
97
+ condition_keys = ["next_task", "exit"]
98
+
99
+ # Create a dynamic literal type from condition keys
100
+ DecisionLiteral = Literal.__getitem__(tuple(condition_keys))
101
+
102
+ class DecisionModel(BaseModel):
103
+ response: str
104
+ decision: DecisionLiteral
105
+
106
+ self.output_pydantic = DecisionModel
107
+
108
+ # If task_type="loop" and output_pydantic is not set
109
+ if self.task_type == "loop" and not self.output_pydantic:
110
+ from pydantic import BaseModel
111
+ from typing import Literal
112
+
113
+ # Gather condition keys for the "decision" field
114
+ condition_keys = list(self.condition.keys())
115
+ if not condition_keys:
116
+ # Fall back to placeholders if nothing is specified
117
+ condition_keys = ["next_item", "exit"]
118
+
119
+ # Create a dynamic literal type
120
+ LoopLiteral = Literal.__getitem__(tuple(condition_keys))
121
+
122
+ class LoopModel(BaseModel):
123
+ response: str
124
+ decision: LoopLiteral
125
+ loop_id: str # Additional field for loop
126
+
127
+ self.output_pydantic = LoopModel
128
+
86
129
  def __str__(self):
87
130
  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}')"
88
131
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: praisonaiagents
3
- Version: 0.0.42
3
+ Version: 0.0.44
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,18 +1,18 @@
1
1
  praisonaiagents/__init__.py,sha256=MCgAj12hVJ0YZmVmdmZgYAAMfPdWSoNSiDlRJCvrJqA,1276
2
- praisonaiagents/main.py,sha256=uMBdwxjnJKHLPUzr_5vXlkuhCUO6EW5O8XC0M-h47sE,13915
2
+ praisonaiagents/main.py,sha256=wcFooRmCY38wtkSNFt6-nhZafgrHotCZE0S6U8IWgUY,14093
3
3
  praisonaiagents/agent/__init__.py,sha256=sKO8wGEXvtCrvV1e834r1Okv0XAqAxqZCqz6hKLiTvA,79
4
4
  praisonaiagents/agent/agent.py,sha256=rWvuZKP1fMF-BBe7I554FVk56Wse_iHyjoRRNEuyC5o,37836
5
5
  praisonaiagents/agents/__init__.py,sha256=_1d6Pqyk9EoBSo7E68sKyd1jDRlN1vxvVIRpoMc0Jcw,168
6
- praisonaiagents/agents/agents.py,sha256=oGCnbyLE22PU1AqXFhJZpzBnoW926mXXfWi-Q2hhklg,33359
6
+ praisonaiagents/agents/agents.py,sha256=J86IWZ8Kv8LulHODX8klwGVGGB2AmmcMzWl9QsEPAv0,33503
7
7
  praisonaiagents/agents/autoagents.py,sha256=bjC2O5oZmoJItJXIMPTWc2lsp_AJC9tMiTQOal2hwPA,13532
8
8
  praisonaiagents/knowledge/__init__.py,sha256=xL1Eh-a3xsHyIcU4foOWF-JdWYIYBALJH9bge0Ujuto,246
9
9
  praisonaiagents/knowledge/chunking.py,sha256=FzoNY0q8MkvG4gADqk4JcRhmH3lcEHbRdonDgitQa30,6624
10
10
  praisonaiagents/knowledge/knowledge.py,sha256=dJd8WuFV3w-zWaSAcT21PcXGRIZZc6YxBsJZDfmZNrk,12358
11
11
  praisonaiagents/memory/memory.py,sha256=ZxqSpOUxk9jeTKGW0ZiTifC0uZtym-EZILP3kuOOKkU,35626
12
12
  praisonaiagents/process/__init__.py,sha256=lkYbL7Hn5a0ldvJtkdH23vfIIZLIcanK-65C0MwaorY,52
13
- praisonaiagents/process/process.py,sha256=uSudOFI1ZlGM_nbT8qHD4iIP3y5Ygu8V-izLot2te70,26316
13
+ praisonaiagents/process/process.py,sha256=_1Nk37kOYakPaUWAJff86rP0ENyykXqMnhTp8E0efuE,30802
14
14
  praisonaiagents/task/__init__.py,sha256=VL5hXVmyGjINb34AalxpBMl-YW9m5EDcRkMTKkSSl7c,80
15
- praisonaiagents/task/task.py,sha256=Tf3CzFNctfTBiy67wfOfT7RnopPGsBntsrHF8012BnE,11383
15
+ praisonaiagents/task/task.py,sha256=VcUl7dVNy5gj3Gq4H9SQU9S3Oy-0qYHSG-lEIlx2Rvg,13085
16
16
  praisonaiagents/tools/__init__.py,sha256=-0lV5n5cG54vYW6REjXIfuJnCLKnfQIDlXsySCaPB9s,7347
17
17
  praisonaiagents/tools/arxiv_tools.py,sha256=1stb31zTjLTon4jCnpZG5de9rKc9QWgC0leLegvPXWo,10528
18
18
  praisonaiagents/tools/calculator_tools.py,sha256=S1xPT74Geurvjm52QMMIG29zDXVEWJmM6nmyY7yF298,9571
@@ -33,7 +33,7 @@ praisonaiagents/tools/wikipedia_tools.py,sha256=pGko-f33wqXgxJTv8db7TbizY5XnzBQR
33
33
  praisonaiagents/tools/xml_tools.py,sha256=iYTMBEk5l3L3ryQ1fkUnNVYK-Nnua2Kx2S0dxNMMs1A,17122
34
34
  praisonaiagents/tools/yaml_tools.py,sha256=uogAZrhXV9O7xvspAtcTfpKSQYL2nlOTvCQXN94-G9A,14215
35
35
  praisonaiagents/tools/yfinance_tools.py,sha256=s2PBj_1v7oQnOobo2fDbQBACEHl61ftG4beG6Z979ZE,8529
36
- praisonaiagents-0.0.42.dist-info/METADATA,sha256=hnuRVdAhcy4TQs7hFrfZGRmgiMzg-VbN9Iym7X1JY-8,664
37
- praisonaiagents-0.0.42.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
38
- praisonaiagents-0.0.42.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
39
- praisonaiagents-0.0.42.dist-info/RECORD,,
36
+ praisonaiagents-0.0.44.dist-info/METADATA,sha256=uT2HYHiW4f0vTb-n0HzzUU2XYCPi2zjsvdBDfAIkcI4,664
37
+ praisonaiagents-0.0.44.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
38
+ praisonaiagents-0.0.44.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
39
+ praisonaiagents-0.0.44.dist-info/RECORD,,