praisonaiagents 0.0.56__tar.gz → 0.0.58__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/PKG-INFO +1 -1
  2. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/llm/llm.py +23 -6
  3. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/process/process.py +352 -112
  4. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/__init__.py +22 -1
  5. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/csv_tools.py +54 -23
  6. praisonaiagents-0.0.58/praisonaiagents/tools/train/data/generatecot.py +500 -0
  7. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents.egg-info/PKG-INFO +1 -1
  8. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents.egg-info/SOURCES.txt +2 -1
  9. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/pyproject.toml +1 -1
  10. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/__init__.py +0 -0
  11. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/agent/__init__.py +0 -0
  12. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/agent/agent.py +0 -0
  13. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/agents/__init__.py +0 -0
  14. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/agents/agents.py +0 -0
  15. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/agents/autoagents.py +0 -0
  16. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/knowledge/__init__.py +0 -0
  17. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/knowledge/chunking.py +0 -0
  18. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/knowledge/knowledge.py +0 -0
  19. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/llm/__init__.py +0 -0
  20. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/main.py +0 -0
  21. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/memory/memory.py +0 -0
  22. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/process/__init__.py +0 -0
  23. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/task/__init__.py +0 -0
  24. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/task/task.py +0 -0
  25. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/arxiv_tools.py +0 -0
  26. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/calculator_tools.py +0 -0
  27. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/duckdb_tools.py +0 -0
  28. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/duckduckgo_tools.py +0 -0
  29. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/excel_tools.py +0 -0
  30. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/file_tools.py +0 -0
  31. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/json_tools.py +0 -0
  32. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/newspaper_tools.py +0 -0
  33. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/pandas_tools.py +0 -0
  34. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/python_tools.py +0 -0
  35. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/shell_tools.py +0 -0
  36. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/spider_tools.py +0 -0
  37. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/test.py +0 -0
  38. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/tools.py +0 -0
  39. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/wikipedia_tools.py +0 -0
  40. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/xml_tools.py +0 -0
  41. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/yaml_tools.py +0 -0
  42. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents/tools/yfinance_tools.py +0 -0
  43. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents.egg-info/dependency_links.txt +0 -0
  44. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents.egg-info/requires.txt +0 -0
  45. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/praisonaiagents.egg-info/top_level.txt +0 -0
  46. {praisonaiagents-0.0.56 → praisonaiagents-0.0.58}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: praisonaiagents
3
- Version: 0.0.56
3
+ Version: 0.0.58
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Dist: pydantic
@@ -113,11 +113,28 @@ class LLM:
113
113
  litellm.success_callback = []
114
114
  litellm._async_success_callback = []
115
115
  litellm.callbacks = []
116
- # Additional logging suppression
117
- litellm.suppress_debug_messages = True
118
- litellm._logging._disable_debugging()
119
- logging.getLogger("litellm.utils").setLevel(logging.WARNING)
120
- logging.getLogger("litellm.main").setLevel(logging.WARNING)
116
+
117
+ verbose = extra_settings.get('verbose', True)
118
+
119
+ # Only suppress logs if not in debug mode
120
+ if not isinstance(verbose, bool) and verbose >= 10:
121
+ # Enable detailed debug logging
122
+ logging.getLogger("asyncio").setLevel(logging.DEBUG)
123
+ logging.getLogger("selector_events").setLevel(logging.DEBUG)
124
+ logging.getLogger("litellm.utils").setLevel(logging.DEBUG)
125
+ logging.getLogger("litellm.main").setLevel(logging.DEBUG)
126
+ litellm.suppress_debug_messages = False
127
+ litellm.set_verbose = True
128
+ else:
129
+ # Suppress debug logging for normal operation
130
+ logging.getLogger("asyncio").setLevel(logging.WARNING)
131
+ logging.getLogger("selector_events").setLevel(logging.WARNING)
132
+ logging.getLogger("litellm.utils").setLevel(logging.WARNING)
133
+ logging.getLogger("litellm.main").setLevel(logging.WARNING)
134
+ litellm.suppress_debug_messages = True
135
+ litellm._logging._disable_debugging()
136
+ warnings.filterwarnings("ignore", category=RuntimeWarning)
137
+
121
138
  except ImportError:
122
139
  raise ImportError(
123
140
  "LiteLLM is required but not installed. "
@@ -145,7 +162,7 @@ class LLM:
145
162
  self.extra_settings = extra_settings
146
163
  self.console = Console()
147
164
  self.chat_history = []
148
- self.verbose = extra_settings.get('verbose', True)
165
+ self.verbose = verbose
149
166
  self.markdown = extra_settings.get('markdown', True)
150
167
  self.self_reflect = extra_settings.get('self_reflect', False)
151
168
  self.max_reflect = extra_settings.get('max_reflect', 3)
@@ -13,6 +13,13 @@ class LoopItems(BaseModel):
13
13
 
14
14
  class Process:
15
15
  def __init__(self, tasks: Dict[str, Task], agents: List[Agent], manager_llm: Optional[str] = None, verbose: bool = False, max_iter: int = 10):
16
+ logging.debug(f"=== Initializing Process ===")
17
+ logging.debug(f"Number of tasks: {len(tasks)}")
18
+ logging.debug(f"Number of agents: {len(agents)}")
19
+ logging.debug(f"Manager LLM: {manager_llm}")
20
+ logging.debug(f"Verbose mode: {verbose}")
21
+ logging.debug(f"Max iterations: {max_iter}")
22
+
16
23
  self.tasks = tasks
17
24
  self.agents = agents
18
25
  self.manager_llm = manager_llm
@@ -21,25 +28,30 @@ class Process:
21
28
 
22
29
  async def aworkflow(self) -> AsyncGenerator[str, None]:
23
30
  """Async version of workflow method"""
31
+ logging.debug("=== Starting Async Workflow ===")
24
32
  current_iter = 0 # Track how many times we've looped
25
33
  # Build workflow relationships first
34
+ logging.debug("Building workflow relationships...")
26
35
  for task in self.tasks.values():
27
36
  if task.next_tasks:
28
37
  for next_task_name in task.next_tasks:
29
38
  next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
30
39
  if next_task:
31
40
  next_task.previous_tasks.append(task.name)
41
+ logging.debug(f"Added {task.name} as previous task for {next_task_name}")
32
42
 
33
43
  # Find start task
44
+ logging.debug("Finding start task...")
34
45
  start_task = None
35
46
  for task_id, task in self.tasks.items():
36
47
  if task.is_start:
37
48
  start_task = task
49
+ logging.debug(f"Found marked start task: {task.name} (id: {task_id})")
38
50
  break
39
51
 
40
52
  if not start_task:
41
53
  start_task = list(self.tasks.values())[0]
42
- logging.info("No start task marked, using first task")
54
+ logging.debug(f"No start task marked, using first task: {start_task.name}")
43
55
 
44
56
  current_task = start_task
45
57
  visited_tasks = set()
@@ -54,7 +66,16 @@ class Process:
54
66
  break
55
67
 
56
68
  task_id = current_task.id
57
- logging.info(f"Executing workflow task: {current_task.name if current_task.name else task_id}")
69
+ logging.debug(f"""
70
+ === Task Execution Details ===
71
+ Current task: {current_task.name}
72
+ Type: {current_task.task_type}
73
+ Status: {current_task.status}
74
+ Previous tasks: {current_task.previous_tasks}
75
+ Next tasks: {current_task.next_tasks}
76
+ Context tasks: {[t.name for t in current_task.context] if current_task.context else []}
77
+ Description length: {len(current_task.description)}
78
+ """)
58
79
 
59
80
  # Add context from previous tasks to description
60
81
  if current_task.previous_tasks or current_task.context:
@@ -66,46 +87,6 @@ class Process:
66
87
  if prev_task and prev_task.result:
67
88
  # Handle loop data
68
89
  if current_task.task_type == "loop":
69
- # # create a loop manager Agent
70
- # loop_manager = Agent(
71
- # name="Loop Manager",
72
- # role="Loop data processor",
73
- # goal="Process loop data and convert it to list format",
74
- # backstory="Expert at handling loop data and converting it to proper format",
75
- # llm=self.manager_llm,
76
- # verbose=self.verbose,
77
- # markdown=True
78
- # )
79
-
80
- # # get the loop data convert it to list using calling Agent class chat
81
- # loop_prompt = f"""
82
- # Process this data into a list format:
83
- # {prev_task.result.raw}
84
-
85
- # Return a JSON object with an 'items' array containing the items to process.
86
- # """
87
- # if current_task.async_execution:
88
- # loop_data_str = await loop_manager.achat(
89
- # prompt=loop_prompt,
90
- # output_json=LoopItems
91
- # )
92
- # else:
93
- # loop_data_str = loop_manager.chat(
94
- # prompt=loop_prompt,
95
- # output_json=LoopItems
96
- # )
97
-
98
- # try:
99
- # # The response will already be parsed into LoopItems model
100
- # loop_data[f"loop_{current_task.name}"] = {
101
- # "items": loop_data_str.items,
102
- # "index": 0,
103
- # "remaining": len(loop_data_str.items)
104
- # }
105
- # context += f"\nCurrent loop item: {loop_data_str.items[0]}"
106
- # except Exception as e:
107
- # display_error(f"Failed to process loop data: {e}")
108
- # context += f"\n{prev_name}: {prev_task.result.raw}"
109
90
  context += f"\n{prev_name}: {prev_task.result.raw}"
110
91
  else:
111
92
  context += f"\n{prev_name}: {prev_task.result.raw}"
@@ -119,14 +100,103 @@ class Process:
119
100
  # Update task description with context
120
101
  current_task.description = current_task.description + context
121
102
 
122
- # Execute task using existing run_task method
123
- yield task_id
124
- visited_tasks.add(task_id)
103
+ # Skip execution for loop tasks, only process their subtasks
104
+ if current_task.task_type == "loop":
105
+ logging.debug(f"""
106
+ === Loop Task Details ===
107
+ Name: {current_task.name}
108
+ ID: {current_task.id}
109
+ Status: {current_task.status}
110
+ Next tasks: {current_task.next_tasks}
111
+ Condition: {current_task.condition}
112
+ Subtasks created: {getattr(current_task, '_subtasks_created', False)}
113
+ Input file: {getattr(current_task, 'input_file', None)}
114
+ """)
115
+
116
+ # Check if subtasks are created and completed
117
+ if getattr(current_task, "_subtasks_created", False):
118
+ subtasks = [
119
+ t for t in self.tasks.values()
120
+ if t.name.startswith(current_task.name + "_")
121
+ ]
122
+ logging.debug(f"""
123
+ === Subtask Status Check ===
124
+ Total subtasks: {len(subtasks)}
125
+ Completed: {sum(1 for st in subtasks if st.status == "completed")}
126
+ Pending: {sum(1 for st in subtasks if st.status != "completed")}
127
+ """)
128
+
129
+ # Log detailed subtask info
130
+ for st in subtasks:
131
+ logging.debug(f"""
132
+ Subtask: {st.name}
133
+ - Status: {st.status}
134
+ - Next tasks: {st.next_tasks}
135
+ - Condition: {st.condition}
136
+ """)
137
+
138
+ if subtasks and all(st.status == "completed" for st in subtasks):
139
+ logging.debug(f"=== All {len(subtasks)} subtasks completed for {current_task.name} ===")
140
+
141
+ # Mark loop task completed and move to next task
142
+ current_task.status = "completed"
143
+ logging.debug(f"Loop {current_task.name} marked as completed")
144
+
145
+ # Move to next task if available
146
+ if current_task.next_tasks:
147
+ next_task_name = current_task.next_tasks[0]
148
+ logging.debug(f"Attempting transition to next task: {next_task_name}")
149
+ next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
150
+ if next_task:
151
+ logging.debug(f"=== Transitioning: {current_task.name} -> {next_task.name} ===")
152
+ logging.debug(f"Next task status: {next_task.status}")
153
+ logging.debug(f"Next task condition: {next_task.condition}")
154
+ current_task = next_task
155
+ else:
156
+ logging.debug(f"=== No next tasks for {current_task.name}, ending loop ===")
157
+ current_task = None
158
+ else:
159
+ logging.debug(f"No subtasks created yet for {current_task.name}")
160
+ # Create subtasks if needed
161
+ if current_task.input_file:
162
+ self._create_loop_subtasks(current_task)
163
+ current_task._subtasks_created = True
164
+ logging.debug(f"Created subtasks from {current_task.input_file}")
165
+ else:
166
+ # No input file, mark as done
167
+ current_task.status = "completed"
168
+ logging.debug(f"No input file, marking {current_task.name} as completed")
169
+ if current_task.next_tasks:
170
+ next_task_name = current_task.next_tasks[0]
171
+ next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
172
+ current_task = next_task
173
+ else:
174
+ current_task = None
175
+ else:
176
+ # Execute non-loop task
177
+ logging.debug(f"=== Executing non-loop task: {current_task.name} (id: {task_id}) ===")
178
+ logging.debug(f"Task status: {current_task.status}")
179
+ logging.debug(f"Task next_tasks: {current_task.next_tasks}")
180
+ yield task_id
181
+ visited_tasks.add(task_id)
125
182
 
126
183
  # Reset completed task to "not started" so it can run again
127
184
  if self.tasks[task_id].status == "completed":
128
- logging.debug(f"Task {task_id} was completed, resetting to 'not started' for next iteration.")
129
- self.tasks[task_id].status = "not started"
185
+ # Never reset loop tasks, decision tasks, or their subtasks
186
+ subtask_name = self.tasks[task_id].name
187
+ logging.debug(f"=== Checking reset for completed task: {subtask_name} ===")
188
+ logging.debug(f"Task type: {self.tasks[task_id].task_type}")
189
+ logging.debug(f"Task status before reset check: {self.tasks[task_id].status}")
190
+
191
+ if (self.tasks[task_id].task_type not in ["loop", "decision"] and
192
+ not any(t.task_type == "loop" and subtask_name.startswith(t.name + "_")
193
+ for t in self.tasks.values())):
194
+ logging.debug(f"=== Resetting non-loop, non-decision task {subtask_name} to 'not started' ===")
195
+ self.tasks[task_id].status = "not started"
196
+ logging.debug(f"Task status after reset: {self.tasks[task_id].status}")
197
+ else:
198
+ logging.debug(f"=== Skipping reset for loop/decision/subtask: {subtask_name} ===")
199
+ logging.debug(f"Keeping status as: {self.tasks[task_id].status}")
130
200
 
131
201
  # Handle loop progression
132
202
  if current_task.task_type == "loop":
@@ -179,6 +249,15 @@ class Process:
179
249
  logging.info("Workflow execution completed")
180
250
  break
181
251
 
252
+ # Add completion logging
253
+ logging.debug(f"""
254
+ === Task Completion ===
255
+ Task: {current_task.name}
256
+ Final status: {current_task.status}
257
+ Next task: {next_task.name if next_task else None}
258
+ Iteration: {current_iter}/{self.max_iter}
259
+ """)
260
+
182
261
  async def asequential(self) -> AsyncGenerator[str, None]:
183
262
  """Async version of sequential method"""
184
263
  for task_id in self.tasks:
@@ -343,33 +422,59 @@ Provide a JSON with the structure:
343
422
  new_tasks = []
344
423
 
345
424
  if file_ext == ".csv":
346
- # existing CSV reading logic
347
425
  with open(start_task.input_file, "r", encoding="utf-8") as f:
348
- # Try as simple CSV first
349
- reader = csv.reader(f)
426
+ reader = csv.reader(f, quotechar='"', escapechar='\\') # Handle quoted/escaped fields
350
427
  previous_task = None
428
+ task_count = 0
429
+
351
430
  for i, row in enumerate(reader):
352
- if row: # Skip empty rows
353
- task_desc = row[0] # Take first column
354
- row_task = Task(
355
- description=f"{start_task.description}\n{task_desc}" if start_task.description else task_desc,
356
- agent=start_task.agent,
357
- name=f"{start_task.name}_{i+1}" if start_task.name else task_desc,
358
- expected_output=getattr(start_task, 'expected_output', None),
359
- is_start=(i == 0),
360
- task_type="task",
361
- condition={
362
- "complete": ["next"],
363
- "retry": ["current"]
364
- }
365
- )
366
- self.tasks[row_task.id] = row_task
367
- new_tasks.append(row_task)
431
+ if not row: # Skip truly empty rows
432
+ continue
368
433
 
369
- if previous_task:
370
- previous_task.next_tasks = [row_task.name]
371
- previous_task.condition["complete"] = [row_task.name]
372
- previous_task = row_task
434
+ # Properly handle Q&A pairs with potential commas
435
+ task_desc = row[0].strip() if row else ""
436
+ if len(row) > 1:
437
+ # Preserve all fields in case of multiple commas
438
+ question = row[0].strip()
439
+ answer = ",".join(field.strip() for field in row[1:])
440
+ task_desc = f"Question: {question}\nAnswer: {answer}"
441
+
442
+ if not task_desc: # Skip rows with empty content
443
+ continue
444
+
445
+ task_count += 1
446
+ logging.debug(f"Processing CSV row {i+1}: {task_desc}")
447
+
448
+ # Inherit next_tasks from parent loop task
449
+ inherited_next_tasks = start_task.next_tasks if start_task.next_tasks else []
450
+
451
+ row_task = Task(
452
+ description=f"{start_task.description}\n{task_desc}" if start_task.description else task_desc,
453
+ agent=start_task.agent,
454
+ name=f"{start_task.name}_{task_count}" if start_task.name else task_desc,
455
+ expected_output=getattr(start_task, 'expected_output', None),
456
+ is_start=(task_count == 1),
457
+ task_type="decision", # Change to decision type
458
+ next_tasks=inherited_next_tasks, # Inherit parent's next tasks
459
+ condition={
460
+ "done": inherited_next_tasks if inherited_next_tasks else ["next"], # Use full inherited_next_tasks
461
+ "retry": ["current"],
462
+ "exit": [] # Empty list for exit condition
463
+ }
464
+ )
465
+ self.tasks[row_task.id] = row_task
466
+ new_tasks.append(row_task)
467
+
468
+ if previous_task:
469
+ previous_task.next_tasks = [row_task.name]
470
+ previous_task.condition["done"] = [row_task.name] # Use "done" consistently
471
+ previous_task = row_task
472
+
473
+ # For the last task in the loop, ensure it points to parent's next tasks
474
+ if task_count > 0 and not row_task.next_tasks:
475
+ row_task.next_tasks = inherited_next_tasks
476
+
477
+ logging.info(f"Processed {task_count} rows from CSV file")
373
478
  else:
374
479
  # If not CSV, read lines
375
480
  with open(start_task.input_file, "r", encoding="utf-8") as f:
@@ -402,7 +507,7 @@ Provide a JSON with the structure:
402
507
  except Exception as e:
403
508
  logging.error(f"Failed to read file tasks: {e}")
404
509
 
405
- # end of the new block
510
+ # end of start task handling
406
511
  current_task = start_task
407
512
  visited_tasks = set()
408
513
  loop_data = {} # Store loop-specific data
@@ -413,8 +518,88 @@ Provide a JSON with the structure:
413
518
  logging.info(f"Max iteration limit {self.max_iter} reached, ending workflow.")
414
519
  break
415
520
 
521
+ # Handle loop task file reading at runtime
522
+ if (current_task.task_type == "loop" and
523
+ current_task is not start_task and
524
+ getattr(current_task, "_subtasks_created", False) is not True):
525
+
526
+ if not current_task.input_file:
527
+ current_task.input_file = "tasks.csv"
528
+
529
+ if getattr(current_task, "input_file", None):
530
+ try:
531
+ file_ext = os.path.splitext(current_task.input_file)[1].lower()
532
+ new_tasks = []
533
+
534
+ if file_ext == ".csv":
535
+ with open(current_task.input_file, "r", encoding="utf-8") as f:
536
+ reader = csv.reader(f)
537
+ previous_task = None
538
+ for i, row in enumerate(reader):
539
+ if row: # Skip empty rows
540
+ task_desc = row[0] # Take first column
541
+ row_task = Task(
542
+ description=f"{current_task.description}\n{task_desc}" if current_task.description else task_desc,
543
+ agent=current_task.agent,
544
+ name=f"{current_task.name}_{i+1}" if current_task.name else task_desc,
545
+ expected_output=getattr(current_task, 'expected_output', None),
546
+ is_start=(i == 0),
547
+ task_type="task",
548
+ condition={
549
+ "complete": ["next"],
550
+ "retry": ["current"]
551
+ }
552
+ )
553
+ self.tasks[row_task.id] = row_task
554
+ new_tasks.append(row_task)
555
+
556
+ if previous_task:
557
+ previous_task.next_tasks = [row_task.name]
558
+ previous_task.condition["complete"] = [row_task.name]
559
+ previous_task = row_task
560
+ else:
561
+ with open(current_task.input_file, "r", encoding="utf-8") as f:
562
+ lines = f.read().splitlines()
563
+ previous_task = None
564
+ for i, line in enumerate(lines):
565
+ row_task = Task(
566
+ description=f"{current_task.description}\n{line.strip()}" if current_task.description else line.strip(),
567
+ agent=current_task.agent,
568
+ name=f"{current_task.name}_{i+1}" if current_task.name else line.strip(),
569
+ expected_output=getattr(current_task, 'expected_output', None),
570
+ is_start=(i == 0),
571
+ task_type="task",
572
+ condition={
573
+ "complete": ["next"],
574
+ "retry": ["current"]
575
+ }
576
+ )
577
+ self.tasks[row_task.id] = row_task
578
+ new_tasks.append(row_task)
579
+
580
+ if previous_task:
581
+ previous_task.next_tasks = [row_task.name]
582
+ previous_task.condition["complete"] = [row_task.name]
583
+ previous_task = row_task
584
+
585
+ if new_tasks:
586
+ current_task.next_tasks = [new_tasks[0].name]
587
+ current_task._subtasks_created = True
588
+ logging.info(f"Created {len(new_tasks)} tasks from: {current_task.input_file} for loop task {current_task.name}")
589
+ except Exception as e:
590
+ logging.error(f"Failed to read file tasks for loop task {current_task.name}: {e}")
591
+
416
592
  task_id = current_task.id
417
- logging.info(f"Executing workflow task: {current_task.name if current_task.name else task_id}")
593
+ logging.debug(f"""
594
+ === Task Execution Details ===
595
+ Current task: {current_task.name}
596
+ Type: {current_task.task_type}
597
+ Status: {current_task.status}
598
+ Previous tasks: {current_task.previous_tasks}
599
+ Next tasks: {current_task.next_tasks}
600
+ Context tasks: {[t.name for t in current_task.context] if current_task.context else []}
601
+ Description length: {len(current_task.description)}
602
+ """)
418
603
 
419
604
  # Add context from previous tasks to description
420
605
  if current_task.previous_tasks or current_task.context:
@@ -426,40 +611,6 @@ Provide a JSON with the structure:
426
611
  if prev_task and prev_task.result:
427
612
  # Handle loop data
428
613
  if current_task.task_type == "loop":
429
- # # create a loop manager Agent
430
- # loop_manager = Agent(
431
- # name="Loop Manager",
432
- # role="Loop data processor",
433
- # goal="Process loop data and convert it to list format",
434
- # backstory="Expert at handling loop data and converting it to proper format",
435
- # llm=self.manager_llm,
436
- # verbose=self.verbose,
437
- # markdown=True
438
- # )
439
-
440
- # # get the loop data convert it to list using calling Agent class chat
441
- # loop_prompt = f"""
442
- # Process this data into a list format:
443
- # {prev_task.result.raw}
444
-
445
- # Return a JSON object with an 'items' array containing the items to process.
446
- # """
447
- # loop_data_str = loop_manager.chat(
448
- # prompt=loop_prompt,
449
- # output_json=LoopItems
450
- # )
451
-
452
- # try:
453
- # # The response will already be parsed into LoopItems model
454
- # loop_data[f"loop_{current_task.name}"] = {
455
- # "items": loop_data_str.items,
456
- # "index": 0,
457
- # "remaining": len(loop_data_str.items)
458
- # }
459
- # context += f"\nCurrent loop item: {loop_data_str.items[0]}"
460
- # except Exception as e:
461
- # display_error(f"Failed to process loop data: {e}")
462
- # context += f"\n{prev_name}: {prev_task.result.raw}"
463
614
  context += f"\n{prev_name}: {prev_task.result.raw}"
464
615
  else:
465
616
  context += f"\n{prev_name}: {prev_task.result.raw}"
@@ -473,14 +624,103 @@ Provide a JSON with the structure:
473
624
  # Update task description with context
474
625
  current_task.description = current_task.description + context
475
626
 
476
- # Execute task using existing run_task method
477
- yield task_id
478
- visited_tasks.add(task_id)
627
+ # Skip execution for loop tasks, only process their subtasks
628
+ if current_task.task_type == "loop":
629
+ logging.debug(f"""
630
+ === Loop Task Details ===
631
+ Name: {current_task.name}
632
+ ID: {current_task.id}
633
+ Status: {current_task.status}
634
+ Next tasks: {current_task.next_tasks}
635
+ Condition: {current_task.condition}
636
+ Subtasks created: {getattr(current_task, '_subtasks_created', False)}
637
+ Input file: {getattr(current_task, 'input_file', None)}
638
+ """)
639
+
640
+ # Check if subtasks are created and completed
641
+ if getattr(current_task, "_subtasks_created", False):
642
+ subtasks = [
643
+ t for t in self.tasks.values()
644
+ if t.name.startswith(current_task.name + "_")
645
+ ]
646
+
647
+ logging.debug(f"""
648
+ === Subtask Status Check ===
649
+ Total subtasks: {len(subtasks)}
650
+ Completed: {sum(1 for st in subtasks if st.status == "completed")}
651
+ Pending: {sum(1 for st in subtasks if st.status != "completed")}
652
+ """)
653
+
654
+ for st in subtasks:
655
+ logging.debug(f"""
656
+ Subtask: {st.name}
657
+ - Status: {st.status}
658
+ - Next tasks: {st.next_tasks}
659
+ - Condition: {st.condition}
660
+ """)
661
+
662
+ if subtasks and all(st.status == "completed" for st in subtasks):
663
+ logging.debug(f"=== All {len(subtasks)} subtasks completed for {current_task.name} ===")
664
+
665
+ # Mark loop task completed and move to next task
666
+ current_task.status = "completed"
667
+ logging.debug(f"Loop {current_task.name} marked as completed")
668
+
669
+ # Move to next task if available
670
+ if current_task.next_tasks:
671
+ next_task_name = current_task.next_tasks[0]
672
+ logging.debug(f"Attempting transition to next task: {next_task_name}")
673
+ next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
674
+ if next_task:
675
+ logging.debug(f"=== Transitioning: {current_task.name} -> {next_task.name} ===")
676
+ logging.debug(f"Next task status: {next_task.status}")
677
+ logging.debug(f"Next task condition: {next_task.condition}")
678
+ current_task = next_task
679
+ else:
680
+ logging.debug(f"=== No next tasks for {current_task.name}, ending loop ===")
681
+ current_task = None
682
+ else:
683
+ logging.debug(f"No subtasks created yet for {current_task.name}")
684
+ # Create subtasks if needed
685
+ if current_task.input_file:
686
+ self._create_loop_subtasks(current_task)
687
+ current_task._subtasks_created = True
688
+ logging.debug(f"Created subtasks from {current_task.input_file}")
689
+ else:
690
+ # No input file, mark as done
691
+ current_task.status = "completed"
692
+ logging.debug(f"No input file, marking {current_task.name} as completed")
693
+ if current_task.next_tasks:
694
+ next_task_name = current_task.next_tasks[0]
695
+ next_task = next((t for t in self.tasks.values() if t.name == next_task_name), None)
696
+ current_task = next_task
697
+ else:
698
+ current_task = None
699
+ else:
700
+ # Execute non-loop task
701
+ logging.debug(f"=== Executing non-loop task: {current_task.name} (id: {task_id}) ===")
702
+ logging.debug(f"Task status: {current_task.status}")
703
+ logging.debug(f"Task next_tasks: {current_task.next_tasks}")
704
+ yield task_id
705
+ visited_tasks.add(task_id)
479
706
 
480
- # Reset completed task to "not started" so it can run again: Only for workflow because some tasks may be revisited
707
+ # Reset completed task to "not started" so it can run again
481
708
  if self.tasks[task_id].status == "completed":
482
- logging.debug(f"Task {task_id} was completed, resetting to 'not started' for next iteration.")
483
- self.tasks[task_id].status = "not started"
709
+ # Never reset loop tasks, decision tasks, or their subtasks
710
+ subtask_name = self.tasks[task_id].name
711
+ logging.debug(f"=== Checking reset for completed task: {subtask_name} ===")
712
+ logging.debug(f"Task type: {self.tasks[task_id].task_type}")
713
+ logging.debug(f"Task status before reset check: {self.tasks[task_id].status}")
714
+
715
+ if (self.tasks[task_id].task_type not in ["loop", "decision"] and
716
+ not any(t.task_type == "loop" and subtask_name.startswith(t.name + "_")
717
+ for t in self.tasks.values())):
718
+ logging.debug(f"=== Resetting non-loop, non-decision task {subtask_name} to 'not started' ===")
719
+ self.tasks[task_id].status = "not started"
720
+ logging.debug(f"Task status after reset: {self.tasks[task_id].status}")
721
+ else:
722
+ logging.debug(f"=== Skipping reset for loop/decision/subtask: {subtask_name} ===")
723
+ logging.debug(f"Keeping status as: {self.tasks[task_id].status}")
484
724
 
485
725
  # Handle loop progression
486
726
  if current_task.task_type == "loop":
@@ -135,6 +135,27 @@ TOOL_MAPPINGS = {
135
135
  'group_by': ('.pandas_tools', 'PandasTools'),
136
136
  'pivot_table': ('.pandas_tools', 'PandasTools'),
137
137
  'pandas_tools': ('.pandas_tools', 'PandasTools'),
138
+
139
+ # Chain of Thought Training Tools
140
+ 'cot_run': ('.train.data.generatecot', 'GenerateCOT'), # Orchestrates text solution
141
+ 'cot_run_dict': ('.train.data.generatecot', 'GenerateCOT'), # Orchestrates dict-based solution
142
+ 'cot_generate': ('.train.data.generatecot', 'GenerateCOT'), # Generate text solution
143
+ 'cot_generate_dict': ('.train.data.generatecot', 'GenerateCOT'), # Generate structured solution
144
+ 'cot_improve': ('.train.data.generatecot', 'GenerateCOT'), # Improve text solution
145
+ 'cot_improve_dict': ('.train.data.generatecot', 'GenerateCOT'), # Improve dict-based solution
146
+ 'cot_check': ('.train.data.generatecot', 'GenerateCOT'), # Check correctness
147
+ 'cot_find_error': ('.train.data.generatecot', 'GenerateCOT'), # Locate error in solution
148
+ 'cot_load_answers': ('.train.data.generatecot', 'GenerateCOT'), # Load QA pairs
149
+
150
+ # COT Save/Export with QA Pairs
151
+ 'cot_save_solutions_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Save with QA pairs
152
+ 'cot_append_solutions_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Append with QA pairs
153
+ 'cot_export_json_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Export JSON with QA pairs
154
+ 'cot_export_csv_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Export CSV with QA pairs
155
+ 'cot_append_csv_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Append CSV with QA pairs
156
+ 'cot_save': ('.train.data.generatecot', 'GenerateCOT'), # Save single QA to file
157
+ 'cot_upload_to_huggingface': ('.train.data.generatecot', 'GenerateCOT'), # Upload dataset to HuggingFace
158
+ 'cot_tools': ('.train.data.generatecot', 'GenerateCOT'), # Full toolkit access
138
159
  }
139
160
 
140
161
  _instances = {} # Cache for class instances
@@ -161,7 +182,7 @@ def __getattr__(name: str) -> Any:
161
182
  ]:
162
183
  return getattr(module, name)
163
184
  if name in ['file_tools', 'pandas_tools', 'wikipedia_tools',
164
- 'newspaper_tools', 'arxiv_tools', 'spider_tools', 'duckdb_tools', 'csv_tools', 'json_tools', 'excel_tools', 'xml_tools', 'yaml_tools', 'calculator_tools', 'python_tools', 'shell_tools']:
185
+ 'newspaper_tools', 'arxiv_tools', 'spider_tools', 'duckdb_tools', 'csv_tools', 'json_tools', 'excel_tools', 'xml_tools', 'yaml_tools', 'calculator_tools', 'python_tools', 'shell_tools', 'cot_tools']:
165
186
  return module # Returns the callable module
166
187
  return getattr(module, name)
167
188
  else: