camel-ai 0.2.71a5__py3-none-any.whl → 0.2.71a7__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.

Potentially problematic release.


This version of camel-ai might be problematic. Click here for more details.

@@ -47,6 +47,9 @@ from camel.societies.workforce.prompts import (
47
47
  )
48
48
  from camel.societies.workforce.role_playing_worker import RolePlayingWorker
49
49
  from camel.societies.workforce.single_agent_worker import SingleAgentWorker
50
+ from camel.societies.workforce.structured_output_handler import (
51
+ StructuredOutputHandler,
52
+ )
50
53
  from camel.societies.workforce.task_channel import TaskChannel
51
54
  from camel.societies.workforce.utils import (
52
55
  FailureContext,
@@ -168,6 +171,14 @@ class Workforce(BaseNode):
168
171
  SingleAgentWorker instances; RolePlayingWorker and nested
169
172
  Workforce instances do not participate in memory sharing.
170
173
  (default: :obj:`False`)
174
+ use_structured_output_handler (bool, optional): Whether to use the
175
+ structured output handler instead of native structured output.
176
+ When enabled, the workforce will use prompts with structured
177
+ output instructions and regex extraction to parse responses.
178
+ This ensures compatibility with agents that don't reliably
179
+ support native structured output. When disabled, the workforce
180
+ uses the native response_format parameter.
181
+ (default: :obj:`True`)
171
182
 
172
183
  Example:
173
184
  >>> import asyncio
@@ -218,6 +229,7 @@ class Workforce(BaseNode):
218
229
  new_worker_agent: Optional[ChatAgent] = None,
219
230
  graceful_shutdown_timeout: float = 15.0,
220
231
  share_memory: bool = False,
232
+ use_structured_output_handler: bool = True,
221
233
  ) -> None:
222
234
  super().__init__(description)
223
235
  self._child_listening_tasks: Deque[
@@ -227,6 +239,9 @@ class Workforce(BaseNode):
227
239
  self.new_worker_agent = new_worker_agent
228
240
  self.graceful_shutdown_timeout = graceful_shutdown_timeout
229
241
  self.share_memory = share_memory
242
+ self.use_structured_output_handler = use_structured_output_handler
243
+ if self.use_structured_output_handler:
244
+ self.structured_handler = StructuredOutputHandler()
230
245
  self.metrics_logger = WorkforceLogger(workforce_id=self.node_id)
231
246
  self._task: Optional[Task] = None
232
247
  self._pending_tasks: Deque[Task] = deque()
@@ -729,12 +744,55 @@ class Workforce(BaseNode):
729
744
  )
730
745
 
731
746
  try:
732
- # Get decision from task agent
733
- self.task_agent.reset()
734
- response = self.task_agent.step(
735
- analysis_prompt, response_format=RecoveryDecision
736
- )
737
- return response.msg.parsed
747
+ # Check if we should use structured handler
748
+ if self.use_structured_output_handler:
749
+ # Use structured handler
750
+ enhanced_prompt = (
751
+ self.structured_handler.generate_structured_prompt(
752
+ base_prompt=analysis_prompt,
753
+ schema=RecoveryDecision,
754
+ examples=[
755
+ {
756
+ "strategy": "RETRY",
757
+ "reasoning": "Temporary network error, "
758
+ "worth retrying",
759
+ "modified_task_content": None,
760
+ }
761
+ ],
762
+ )
763
+ )
764
+
765
+ self.task_agent.reset()
766
+ response = self.task_agent.step(enhanced_prompt)
767
+
768
+ result = self.structured_handler.parse_structured_response(
769
+ response.msg.content if response.msg else "",
770
+ schema=RecoveryDecision,
771
+ fallback_values={
772
+ "strategy": RecoveryStrategy.RETRY,
773
+ "reasoning": "Defaulting to retry due to parsing "
774
+ "issues",
775
+ "modified_task_content": None,
776
+ },
777
+ )
778
+ # Ensure we return a RecoveryDecision instance
779
+ if isinstance(result, RecoveryDecision):
780
+ return result
781
+ elif isinstance(result, dict):
782
+ return RecoveryDecision(**result)
783
+ else:
784
+ return RecoveryDecision(
785
+ strategy=RecoveryStrategy.RETRY,
786
+ reasoning="Failed to parse recovery decision",
787
+ modified_task_content=None,
788
+ )
789
+ else:
790
+ # Use existing native structured output code
791
+ self.task_agent.reset()
792
+ response = self.task_agent.step(
793
+ analysis_prompt, response_format=RecoveryDecision
794
+ )
795
+ return response.msg.parsed
738
796
 
739
797
  except Exception as e:
740
798
  logger.warning(
@@ -1338,6 +1396,9 @@ class Workforce(BaseNode):
1338
1396
  start_coroutine, self._loop
1339
1397
  )
1340
1398
  self._child_listening_tasks.append(child_task)
1399
+ else:
1400
+ # Close the coroutine to prevent RuntimeWarning
1401
+ start_coroutine.close()
1341
1402
 
1342
1403
  def add_single_agent_worker(
1343
1404
  self,
@@ -1372,6 +1433,7 @@ class Workforce(BaseNode):
1372
1433
  description=description,
1373
1434
  worker=worker,
1374
1435
  pool_max_size=pool_max_size,
1436
+ use_structured_output_handler=self.use_structured_output_handler,
1375
1437
  )
1376
1438
  self._children.append(worker_node)
1377
1439
 
@@ -1450,6 +1512,7 @@ class Workforce(BaseNode):
1450
1512
  user_agent_kwargs=user_agent_kwargs,
1451
1513
  summarize_agent_kwargs=summarize_agent_kwargs,
1452
1514
  chat_turn_limit=chat_turn_limit,
1515
+ use_structured_output_handler=self.use_structured_output_handler,
1453
1516
  )
1454
1517
  self._children.append(worker_node)
1455
1518
 
@@ -1623,26 +1686,73 @@ class Workforce(BaseNode):
1623
1686
  )
1624
1687
  prompt = prompt + f"\n\n{feedback}"
1625
1688
 
1626
- response = self.coordinator_agent.step(
1627
- prompt, response_format=TaskAssignResult
1628
- )
1629
-
1630
- if response.msg is None or response.msg.content is None:
1631
- logger.error(
1632
- "Coordinator agent returned empty response for task assignment"
1689
+ # Check if we should use structured handler
1690
+ if self.use_structured_output_handler:
1691
+ # Use structured handler for prompt-based extraction
1692
+ enhanced_prompt = (
1693
+ self.structured_handler.generate_structured_prompt(
1694
+ base_prompt=prompt,
1695
+ schema=TaskAssignResult,
1696
+ examples=[
1697
+ {
1698
+ "assignments": [
1699
+ {
1700
+ "task_id": "task_1",
1701
+ "assignee_id": "worker_123",
1702
+ "dependencies": [],
1703
+ }
1704
+ ]
1705
+ }
1706
+ ],
1707
+ )
1633
1708
  )
1634
- return TaskAssignResult(assignments=[])
1635
1709
 
1636
- try:
1637
- result_dict = json.loads(response.msg.content, parse_int=str)
1638
- return TaskAssignResult(**result_dict)
1639
- except json.JSONDecodeError as e:
1640
- logger.error(
1641
- f"JSON parsing error in task assignment: Invalid response "
1642
- f"format - {e}. Response content: "
1643
- f"{response.msg.content[:50]}..."
1710
+ # Get response without structured format
1711
+ response = self.coordinator_agent.step(enhanced_prompt)
1712
+
1713
+ if response.msg is None or response.msg.content is None:
1714
+ logger.error(
1715
+ "Coordinator agent returned empty response for "
1716
+ "task assignment"
1717
+ )
1718
+ return TaskAssignResult(assignments=[])
1719
+
1720
+ # Parse with structured handler
1721
+ result = self.structured_handler.parse_structured_response(
1722
+ response.msg.content,
1723
+ schema=TaskAssignResult,
1724
+ fallback_values={"assignments": []},
1725
+ )
1726
+ # Ensure we return a TaskAssignResult instance
1727
+ if isinstance(result, TaskAssignResult):
1728
+ return result
1729
+ elif isinstance(result, dict):
1730
+ return TaskAssignResult(**result)
1731
+ else:
1732
+ return TaskAssignResult(assignments=[])
1733
+ else:
1734
+ # Use existing native structured output code
1735
+ response = self.coordinator_agent.step(
1736
+ prompt, response_format=TaskAssignResult
1644
1737
  )
1645
- return TaskAssignResult(assignments=[])
1738
+
1739
+ if response.msg is None or response.msg.content is None:
1740
+ logger.error(
1741
+ "Coordinator agent returned empty response for "
1742
+ "task assignment"
1743
+ )
1744
+ return TaskAssignResult(assignments=[])
1745
+
1746
+ try:
1747
+ result_dict = json.loads(response.msg.content, parse_int=str)
1748
+ return TaskAssignResult(**result_dict)
1749
+ except json.JSONDecodeError as e:
1750
+ logger.error(
1751
+ f"JSON parsing error in task assignment: Invalid response "
1752
+ f"format - {e}. Response content: "
1753
+ f"{response.msg.content[:50]}..."
1754
+ )
1755
+ return TaskAssignResult(assignments=[])
1646
1756
 
1647
1757
  def _validate_assignments(
1648
1758
  self, assignments: List[TaskAssignment], valid_ids: Set[str]
@@ -1844,6 +1954,10 @@ class Workforce(BaseNode):
1844
1954
  logger.error(
1845
1955
  f"Failed to post task {task.id} to {assignee_id}: {e}"
1846
1956
  )
1957
+ print(
1958
+ f"{Fore.RED}Failed to post task {task.id} to {assignee_id}: "
1959
+ f"{e}{Fore.RESET}"
1960
+ )
1847
1961
 
1848
1962
  async def _post_dependency(self, dependency: Task) -> None:
1849
1963
  await self._channel.post_dependency(dependency, self.node_id)
@@ -1864,35 +1978,92 @@ class Workforce(BaseNode):
1864
1978
  child_nodes_info=self._get_child_nodes_info(),
1865
1979
  additional_info=task.additional_info,
1866
1980
  )
1867
- response = self.coordinator_agent.step(
1868
- prompt, response_format=WorkerConf
1869
- )
1870
- if response.msg is None or response.msg.content is None:
1871
- logger.error(
1872
- "Coordinator agent returned empty response for worker creation"
1873
- )
1874
- # Create a fallback worker configuration
1875
- new_node_conf = WorkerConf(
1876
- description=f"Fallback worker for "
1877
- f"task: {task.content[:50]}...",
1878
- role="General Assistant",
1879
- sys_msg="You are a general assistant that can help "
1880
- "with various tasks.",
1981
+ # Check if we should use structured handler
1982
+ if self.use_structured_output_handler:
1983
+ # Use structured handler
1984
+ enhanced_prompt = (
1985
+ self.structured_handler.generate_structured_prompt(
1986
+ base_prompt=prompt,
1987
+ schema=WorkerConf,
1988
+ examples=[
1989
+ {
1990
+ "description": "Data analysis specialist",
1991
+ "role": "Data Analyst",
1992
+ "sys_msg": "You are an expert data analyst.",
1993
+ }
1994
+ ],
1995
+ )
1881
1996
  )
1997
+
1998
+ response = self.coordinator_agent.step(enhanced_prompt)
1999
+
2000
+ if response.msg is None or response.msg.content is None:
2001
+ logger.error(
2002
+ "Coordinator agent returned empty response for "
2003
+ "worker creation"
2004
+ )
2005
+ new_node_conf = WorkerConf(
2006
+ description=f"Fallback worker for task: "
2007
+ f"{task.content[:50]}...",
2008
+ role="General Assistant",
2009
+ sys_msg="You are a general assistant that can help "
2010
+ "with various tasks.",
2011
+ )
2012
+ else:
2013
+ result = self.structured_handler.parse_structured_response(
2014
+ response.msg.content,
2015
+ schema=WorkerConf,
2016
+ fallback_values={
2017
+ "description": f"Worker for task: "
2018
+ f"{task.content[:50]}...",
2019
+ "role": "Task Specialist",
2020
+ "sys_msg": f"You are a specialist for: {task.content}",
2021
+ },
2022
+ )
2023
+ # Ensure we have a WorkerConf instance
2024
+ if isinstance(result, WorkerConf):
2025
+ new_node_conf = result
2026
+ elif isinstance(result, dict):
2027
+ new_node_conf = WorkerConf(**result)
2028
+ else:
2029
+ new_node_conf = WorkerConf(
2030
+ description=f"Worker for task: {task.content[:50]}...",
2031
+ role="Task Specialist",
2032
+ sys_msg=f"You are a specialist for: {task.content}",
2033
+ )
1882
2034
  else:
1883
- try:
1884
- result_dict = json.loads(response.msg.content)
1885
- new_node_conf = WorkerConf(**result_dict)
1886
- except json.JSONDecodeError as e:
2035
+ # Use existing native structured output code
2036
+ response = self.coordinator_agent.step(
2037
+ prompt, response_format=WorkerConf
2038
+ )
2039
+ if response.msg is None or response.msg.content is None:
1887
2040
  logger.error(
1888
- f"JSON parsing error in worker creation: Invalid response "
1889
- f"format - {e}. Response content: "
1890
- f"{response.msg.content[:100]}..."
2041
+ "Coordinator agent returned empty response for "
2042
+ "worker creation"
1891
2043
  )
1892
- raise RuntimeError(
1893
- f"Failed to create worker for task {task.id}: "
1894
- f"Coordinator agent returned malformed JSON response. "
2044
+ # Create a fallback worker configuration
2045
+ new_node_conf = WorkerConf(
2046
+ description=f"Fallback worker for "
2047
+ f"task: {task.content[:50]}...",
2048
+ role="General Assistant",
2049
+ sys_msg="You are a general assistant that can help "
2050
+ "with various tasks.",
1895
2051
  )
2052
+ else:
2053
+ try:
2054
+ result_dict = json.loads(response.msg.content)
2055
+ new_node_conf = WorkerConf(**result_dict)
2056
+ except json.JSONDecodeError as e:
2057
+ logger.error(
2058
+ f"JSON parsing error in worker creation: Invalid "
2059
+ f"response format - {e}. Response content: "
2060
+ f"format - {e}. Response content: "
2061
+ f"{response.msg.content[:100]}..."
2062
+ )
2063
+ raise RuntimeError(
2064
+ f"Failed to create worker for task {task.id}: "
2065
+ f"Coordinator agent returned malformed JSON response. "
2066
+ ) from e
1896
2067
 
1897
2068
  new_agent = await self._create_new_agent(
1898
2069
  new_node_conf.role,
@@ -1903,6 +2074,7 @@ class Workforce(BaseNode):
1903
2074
  description=new_node_conf.description,
1904
2075
  worker=new_agent,
1905
2076
  pool_max_size=DEFAULT_WORKER_POOL_SIZE,
2077
+ use_structured_output_handler=self.use_structured_output_handler,
1906
2078
  )
1907
2079
  new_node.set_channel(self._channel)
1908
2080
 
@@ -1973,17 +2145,7 @@ class Workforce(BaseNode):
1973
2145
  f"Current pending tasks: {len(self._pending_tasks)}, "
1974
2146
  f"In-flight tasks: {self._in_flight_tasks}"
1975
2147
  )
1976
- logger.warning(error_msg)
1977
-
1978
- if self._pending_tasks and self._assignees:
1979
- for task in self._pending_tasks:
1980
- if task.id in self._assignees:
1981
- # Mark task as failed and decrement counter
1982
- task.set_state(TaskState.FAILED)
1983
- self._decrement_in_flight_tasks(
1984
- task.id, "timeout/error in _get_returned_task"
1985
- )
1986
- return task
2148
+ logger.error(error_msg, exc_info=True)
1987
2149
  return None
1988
2150
 
1989
2151
  async def _post_ready_tasks(self) -> None:
@@ -2059,12 +2221,8 @@ class Workforce(BaseNode):
2059
2221
  task.failure_count += 1
2060
2222
 
2061
2223
  # Determine detailed failure information
2062
- if is_task_result_insufficient(task):
2063
- failure_reason = "Worker returned unhelpful "
2064
- f"response: {task.result[:100] if task.result else ''}..."
2065
- else:
2066
- failure_reason = "Task marked as failed despite "
2067
- f"having result: {(task.result or '')[:100]}..."
2224
+ # Use the actual error/result stored in task.result
2225
+ failure_reason = task.result or "Unknown error"
2068
2226
 
2069
2227
  # Add context about the worker and task
2070
2228
  worker_id = task.assigned_worker_id or "unknown"
@@ -2129,101 +2287,107 @@ class Workforce(BaseNode):
2129
2287
  f"{recovery_decision.reasoning}"
2130
2288
  )
2131
2289
 
2132
- if recovery_decision.strategy == RecoveryStrategy.RETRY:
2133
- # Simply retry the task by reposting it
2134
- if task.id in self._assignees:
2135
- assignee_id = self._assignees[task.id]
2136
- await self._post_task(task, assignee_id)
2137
- action_taken = f"retried with same worker {assignee_id}"
2138
- else:
2139
- # Find a new assignee and retry
2140
- batch_result = await self._find_assignee([task])
2141
- assignment = batch_result.assignments[0]
2142
- self._assignees[task.id] = assignment.assignee_id
2143
- await self._post_task(task, assignment.assignee_id)
2144
- action_taken = (
2145
- f"retried with new worker {assignment.assignee_id}"
2146
- )
2290
+ # Clean up tracking before attempting recovery
2291
+ if task.id in self._assignees:
2292
+ await self._channel.archive_task(task.id)
2293
+ self._cleanup_task_tracking(task.id)
2147
2294
 
2148
- elif recovery_decision.strategy == RecoveryStrategy.REPLAN:
2149
- # Modify the task content and retry
2150
- if recovery_decision.modified_task_content:
2151
- task.content = recovery_decision.modified_task_content
2152
- logger.info(f"Task {task.id} content modified for replan")
2295
+ try:
2296
+ if recovery_decision.strategy == RecoveryStrategy.RETRY:
2297
+ # Simply retry the task by reposting it
2298
+ if task.id in self._assignees:
2299
+ assignee_id = self._assignees[task.id]
2300
+ await self._post_task(task, assignee_id)
2301
+ action_taken = f"retried with same worker {assignee_id}"
2302
+ else:
2303
+ # Find a new assignee and retry
2304
+ batch_result = await self._find_assignee([task])
2305
+ assignment = batch_result.assignments[0]
2306
+ self._assignees[task.id] = assignment.assignee_id
2307
+ await self._post_task(task, assignment.assignee_id)
2308
+ action_taken = (
2309
+ f"retried with new worker {assignment.assignee_id}"
2310
+ )
2153
2311
 
2154
- # Repost the modified task
2155
- if task.id in self._assignees:
2156
- assignee_id = self._assignees[task.id]
2157
- await self._post_task(task, assignee_id)
2158
- action_taken = (
2159
- f"replanned and retried with worker {assignee_id}"
2160
- )
2161
- else:
2162
- # Find a new assignee for the replanned task
2163
- batch_result = await self._find_assignee([task])
2164
- assignment = batch_result.assignments[0]
2165
- self._assignees[task.id] = assignment.assignee_id
2166
- await self._post_task(task, assignment.assignee_id)
2167
- action_taken = (
2168
- f"replanned and assigned to "
2169
- f"worker {assignment.assignee_id}"
2170
- )
2312
+ elif recovery_decision.strategy == RecoveryStrategy.REPLAN:
2313
+ # Modify the task content and retry
2314
+ if recovery_decision.modified_task_content:
2315
+ task.content = recovery_decision.modified_task_content
2316
+ logger.info(f"Task {task.id} content modified for replan")
2171
2317
 
2172
- elif recovery_decision.strategy == RecoveryStrategy.DECOMPOSE:
2173
- # Decompose the task into subtasks
2174
- subtasks = self._decompose_task(task)
2175
- if self.metrics_logger and subtasks:
2176
- self.metrics_logger.log_task_decomposed(
2177
- parent_task_id=task.id,
2178
- subtask_ids=[st.id for st in subtasks],
2179
- )
2180
- for subtask in subtasks:
2181
- self.metrics_logger.log_task_created(
2182
- task_id=subtask.id,
2183
- description=subtask.content,
2184
- parent_task_id=task.id,
2185
- task_type=subtask.type,
2186
- metadata=subtask.additional_info,
2318
+ # Repost the modified task
2319
+ if task.id in self._assignees:
2320
+ assignee_id = self._assignees[task.id]
2321
+ await self._post_task(task, assignee_id)
2322
+ action_taken = (
2323
+ f"replanned and retried with worker {assignee_id}"
2324
+ )
2325
+ else:
2326
+ # Find a new assignee for the replanned task
2327
+ batch_result = await self._find_assignee([task])
2328
+ assignment = batch_result.assignments[0]
2329
+ self._assignees[task.id] = assignment.assignee_id
2330
+ await self._post_task(task, assignment.assignee_id)
2331
+ action_taken = (
2332
+ f"replanned and assigned to "
2333
+ f"worker {assignment.assignee_id}"
2187
2334
  )
2188
- # Insert packets at the head of the queue
2189
- self._pending_tasks.extendleft(reversed(subtasks))
2190
-
2191
- await self._post_ready_tasks()
2192
- action_taken = f"decomposed into {len(subtasks)} subtasks"
2193
2335
 
2194
- # Handle task completion differently for decomposed tasks
2195
- if task.id in self._assignees:
2196
- await self._channel.archive_task(task.id)
2336
+ elif recovery_decision.strategy == RecoveryStrategy.DECOMPOSE:
2337
+ # Decompose the task into subtasks
2338
+ subtasks = self._decompose_task(task)
2339
+ if self.metrics_logger and subtasks:
2340
+ self.metrics_logger.log_task_decomposed(
2341
+ parent_task_id=task.id,
2342
+ subtask_ids=[st.id for st in subtasks],
2343
+ )
2344
+ for subtask in subtasks:
2345
+ self.metrics_logger.log_task_created(
2346
+ task_id=subtask.id,
2347
+ description=subtask.content,
2348
+ parent_task_id=task.id,
2349
+ task_type=subtask.type,
2350
+ metadata=subtask.additional_info,
2351
+ )
2352
+ # Insert packets at the head of the queue
2353
+ self._pending_tasks.extendleft(reversed(subtasks))
2197
2354
 
2198
- self._cleanup_task_tracking(task.id)
2199
- logger.debug(
2200
- f"Task {task.id} failed and was {action_taken}. "
2201
- f"Dependencies updated for subtasks."
2202
- )
2355
+ await self._post_ready_tasks()
2356
+ action_taken = f"decomposed into {len(subtasks)} subtasks"
2203
2357
 
2204
- # Sync shared memory after task decomposition
2205
- if self.share_memory:
2206
- logger.info(
2207
- f"Syncing shared memory after task {task.id} decomposition"
2358
+ logger.debug(
2359
+ f"Task {task.id} failed and was {action_taken}. "
2360
+ f"Dependencies updated for subtasks."
2208
2361
  )
2209
- self._sync_shared_memory()
2210
2362
 
2211
- # Check if any pending tasks are now ready to execute
2212
- await self._post_ready_tasks()
2213
- return False
2363
+ # Sync shared memory after task decomposition
2364
+ if self.share_memory:
2365
+ logger.info(
2366
+ f"Syncing shared memory after "
2367
+ f"task {task.id} decomposition"
2368
+ )
2369
+ self._sync_shared_memory()
2214
2370
 
2215
- elif recovery_decision.strategy == RecoveryStrategy.CREATE_WORKER:
2216
- assignee = await self._create_worker_node_for_task(task)
2217
- await self._post_task(task, assignee.node_id)
2218
- action_taken = (
2219
- f"created new worker {assignee.node_id} and assigned "
2220
- f"task {task.id} to it"
2221
- )
2371
+ # Check if any pending tasks are now ready to execute
2372
+ await self._post_ready_tasks()
2373
+ return False
2222
2374
 
2223
- if task.id in self._assignees:
2224
- await self._channel.archive_task(task.id)
2375
+ elif recovery_decision.strategy == RecoveryStrategy.CREATE_WORKER:
2376
+ assignee = await self._create_worker_node_for_task(task)
2377
+ await self._post_task(task, assignee.node_id)
2378
+ action_taken = (
2379
+ f"created new worker {assignee.node_id} and assigned "
2380
+ f"task {task.id} to it"
2381
+ )
2382
+ except Exception as e:
2383
+ logger.error(f"Recovery strategy failed for task {task.id}: {e}")
2384
+ # If max retries reached, halt the workforce
2385
+ if task.failure_count >= MAX_TASK_RETRIES:
2386
+ self._completed_tasks.append(task)
2387
+ return True
2388
+ self._completed_tasks.append(task)
2389
+ return False
2225
2390
 
2226
- self._cleanup_task_tracking(task.id)
2227
2391
  logger.debug(
2228
2392
  f"Task {task.id} failed and was {action_taken}. "
2229
2393
  f"Updating dependency state."
@@ -2716,6 +2880,7 @@ class Workforce(BaseNode):
2716
2880
  else None,
2717
2881
  graceful_shutdown_timeout=self.graceful_shutdown_timeout,
2718
2882
  share_memory=self.share_memory,
2883
+ use_structured_output_handler=self.use_structured_output_handler,
2719
2884
  )
2720
2885
 
2721
2886
  for child in self._children:
camel/tasks/task.py CHANGED
@@ -184,7 +184,7 @@ def parse_response(
184
184
  tasks = []
185
185
  if task_id is None:
186
186
  task_id = "0"
187
- for i, content in enumerate(tasks_content):
187
+ for i, content in enumerate(tasks_content, 1):
188
188
  stripped_content = content.strip()
189
189
  # validate subtask content before creating the task
190
190
  if validate_task_content(stripped_content, f"{task_id}.{i}"):