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

Files changed (36) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +1533 -135
  3. camel/agents/repo_agent.py +2 -1
  4. camel/benchmarks/browsecomp.py +6 -6
  5. camel/logger.py +1 -1
  6. camel/messages/base.py +12 -1
  7. camel/models/azure_openai_model.py +96 -7
  8. camel/models/base_model.py +68 -10
  9. camel/models/deepseek_model.py +5 -0
  10. camel/models/gemini_model.py +5 -0
  11. camel/models/litellm_model.py +48 -16
  12. camel/models/model_manager.py +24 -6
  13. camel/models/openai_compatible_model.py +109 -5
  14. camel/models/openai_model.py +117 -8
  15. camel/societies/workforce/prompts.py +68 -5
  16. camel/societies/workforce/role_playing_worker.py +65 -7
  17. camel/societies/workforce/single_agent_worker.py +72 -18
  18. camel/societies/workforce/structured_output_handler.py +500 -0
  19. camel/societies/workforce/utils.py +67 -2
  20. camel/societies/workforce/workforce.py +527 -114
  21. camel/societies/workforce/workforce_logger.py +0 -8
  22. camel/tasks/task.py +3 -1
  23. camel/toolkits/__init__.py +2 -0
  24. camel/toolkits/file_write_toolkit.py +526 -121
  25. camel/toolkits/hybrid_browser_toolkit/actions.py +235 -60
  26. camel/toolkits/hybrid_browser_toolkit/agent.py +25 -8
  27. camel/toolkits/hybrid_browser_toolkit/browser_session.py +574 -164
  28. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +996 -126
  29. camel/toolkits/hybrid_browser_toolkit/stealth_config.py +116 -0
  30. camel/toolkits/hybrid_browser_toolkit/stealth_script.js +0 -0
  31. camel/toolkits/message_agent_toolkit.py +608 -0
  32. camel/toolkits/note_taking_toolkit.py +7 -13
  33. {camel_ai-0.2.71a4.dist-info → camel_ai-0.2.71a6.dist-info}/METADATA +6 -4
  34. {camel_ai-0.2.71a4.dist-info → camel_ai-0.2.71a6.dist-info}/RECORD +36 -32
  35. {camel_ai-0.2.71a4.dist-info → camel_ai-0.2.71a6.dist-info}/WHEEL +0 -0
  36. {camel_ai-0.2.71a4.dist-info → camel_ai-0.2.71a6.dist-info}/licenses/LICENSE +0 -0
@@ -17,6 +17,10 @@ from json import JSONDecodeError
17
17
  from typing import Any, Dict, List, Optional, Type, Union
18
18
 
19
19
  from openai import AsyncOpenAI, AsyncStream, BadRequestError, OpenAI, Stream
20
+ from openai.lib.streaming.chat import (
21
+ AsyncChatCompletionStreamManager,
22
+ ChatCompletionStreamManager,
23
+ )
20
24
  from pydantic import BaseModel, ValidationError
21
25
 
22
26
  from camel.logger import get_logger
@@ -41,6 +45,11 @@ if os.environ.get("LANGFUSE_ENABLED", "False").lower() == "true":
41
45
  from langfuse.decorators import observe
42
46
  except ImportError:
43
47
  from camel.utils import observe
48
+ elif os.environ.get("TRACEROOT_ENABLED", "False").lower() == "true":
49
+ try:
50
+ from traceroot import trace as observe # type: ignore[import]
51
+ except ImportError:
52
+ from camel.utils import observe
44
53
  else:
45
54
  from camel.utils import observe
46
55
 
@@ -138,7 +147,11 @@ class OpenAICompatibleModel(BaseModelBackend):
138
147
  messages: List[OpenAIMessage],
139
148
  response_format: Optional[Type[BaseModel]] = None,
140
149
  tools: Optional[List[Dict[str, Any]]] = None,
141
- ) -> Union[ChatCompletion, Stream[ChatCompletionChunk]]:
150
+ ) -> Union[
151
+ ChatCompletion,
152
+ Stream[ChatCompletionChunk],
153
+ ChatCompletionStreamManager[BaseModel],
154
+ ]:
142
155
  r"""Runs inference of OpenAI chat completion.
143
156
 
144
157
  Args:
@@ -153,6 +166,8 @@ class OpenAICompatibleModel(BaseModelBackend):
153
166
  Union[ChatCompletion, Stream[ChatCompletionChunk]]:
154
167
  `ChatCompletion` in the non-stream mode, or
155
168
  `Stream[ChatCompletionChunk]` in the stream mode.
169
+ `ChatCompletionStreamManager[BaseModel]` for
170
+ structured output streaming.
156
171
  """
157
172
 
158
173
  # Update Langfuse trace with current agent session and metadata
@@ -170,10 +185,22 @@ class OpenAICompatibleModel(BaseModelBackend):
170
185
  response_format = response_format or self.model_config_dict.get(
171
186
  "response_format", None
172
187
  )
188
+
189
+ # Check if streaming is enabled
190
+ is_streaming = self.model_config_dict.get("stream", False)
191
+
173
192
  if response_format:
174
193
  result: Union[ChatCompletion, Stream[ChatCompletionChunk]] = (
175
194
  self._request_parse(messages, response_format, tools)
176
195
  )
196
+ if is_streaming:
197
+ # Use streaming parse for structured output
198
+ return self._request_stream_parse(
199
+ messages, response_format, tools
200
+ )
201
+ else:
202
+ # Use non-streaming parse for structured output
203
+ return self._request_parse(messages, response_format, tools)
177
204
  else:
178
205
  result = self._request_chat_completion(messages, tools)
179
206
 
@@ -185,7 +212,11 @@ class OpenAICompatibleModel(BaseModelBackend):
185
212
  messages: List[OpenAIMessage],
186
213
  response_format: Optional[Type[BaseModel]] = None,
187
214
  tools: Optional[List[Dict[str, Any]]] = None,
188
- ) -> Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
215
+ ) -> Union[
216
+ ChatCompletion,
217
+ AsyncStream[ChatCompletionChunk],
218
+ AsyncChatCompletionStreamManager[BaseModel],
219
+ ]:
189
220
  r"""Runs inference of OpenAI chat completion in async mode.
190
221
 
191
222
  Args:
@@ -197,9 +228,12 @@ class OpenAICompatibleModel(BaseModelBackend):
197
228
  use for the request.
198
229
 
199
230
  Returns:
200
- Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
201
- `ChatCompletion` in the non-stream mode, or
202
- `AsyncStream[ChatCompletionChunk]` in the stream mode.
231
+ Union[ChatCompletion, AsyncStream[ChatCompletionChunk],
232
+ AsyncChatCompletionStreamManager[BaseModel]]:
233
+ `ChatCompletion` in the non-stream mode,
234
+ `AsyncStream[ChatCompletionChunk]` in the stream mode,
235
+ or `AsyncChatCompletionStreamManager[BaseModel]` for
236
+ structured output streaming.
203
237
  """
204
238
 
205
239
  # Update Langfuse trace with current agent session and metadata
@@ -217,10 +251,24 @@ class OpenAICompatibleModel(BaseModelBackend):
217
251
  response_format = response_format or self.model_config_dict.get(
218
252
  "response_format", None
219
253
  )
254
+
255
+ # Check if streaming is enabled
256
+ is_streaming = self.model_config_dict.get("stream", False)
257
+
220
258
  if response_format:
221
259
  result: Union[
222
260
  ChatCompletion, AsyncStream[ChatCompletionChunk]
223
261
  ] = await self._arequest_parse(messages, response_format, tools)
262
+ if is_streaming:
263
+ # Use streaming parse for structured output
264
+ return await self._arequest_stream_parse(
265
+ messages, response_format, tools
266
+ )
267
+ else:
268
+ # Use non-streaming parse for structured output
269
+ return await self._arequest_parse(
270
+ messages, response_format, tools
271
+ )
224
272
  else:
225
273
  result = await self._arequest_chat_completion(messages, tools)
226
274
 
@@ -336,6 +384,62 @@ class OpenAICompatibleModel(BaseModelBackend):
336
384
  logger.error(f"Fallback attempt also failed: {e}")
337
385
  raise
338
386
 
387
+ def _request_stream_parse(
388
+ self,
389
+ messages: List[OpenAIMessage],
390
+ response_format: Type[BaseModel],
391
+ tools: Optional[List[Dict[str, Any]]] = None,
392
+ ) -> ChatCompletionStreamManager[BaseModel]:
393
+ r"""Request streaming structured output parsing.
394
+
395
+ Note: This uses OpenAI's beta streaming API for structured outputs.
396
+ """
397
+ import copy
398
+
399
+ request_config = copy.deepcopy(self.model_config_dict)
400
+
401
+ # Remove stream from config as it's handled by the stream method
402
+ request_config.pop("stream", None)
403
+
404
+ if tools is not None:
405
+ request_config["tools"] = tools
406
+
407
+ # Use the beta streaming API for structured outputs
408
+ return self._client.beta.chat.completions.stream(
409
+ messages=messages,
410
+ model=self.model_type,
411
+ response_format=response_format,
412
+ **request_config,
413
+ )
414
+
415
+ async def _arequest_stream_parse(
416
+ self,
417
+ messages: List[OpenAIMessage],
418
+ response_format: Type[BaseModel],
419
+ tools: Optional[List[Dict[str, Any]]] = None,
420
+ ) -> AsyncChatCompletionStreamManager[BaseModel]:
421
+ r"""Request async streaming structured output parsing.
422
+
423
+ Note: This uses OpenAI's beta streaming API for structured outputs.
424
+ """
425
+ import copy
426
+
427
+ request_config = copy.deepcopy(self.model_config_dict)
428
+
429
+ # Remove stream from config as it's handled by the stream method
430
+ request_config.pop("stream", None)
431
+
432
+ if tools is not None:
433
+ request_config["tools"] = tools
434
+
435
+ # Use the beta streaming API for structured outputs
436
+ return self._async_client.beta.chat.completions.stream(
437
+ messages=messages,
438
+ model=self.model_type,
439
+ response_format=response_format,
440
+ **request_config,
441
+ )
442
+
339
443
  @property
340
444
  def token_counter(self) -> BaseTokenCounter:
341
445
  r"""Initialize the token counter for the model backend.
@@ -16,6 +16,10 @@ import warnings
16
16
  from typing import Any, Dict, List, Optional, Type, Union
17
17
 
18
18
  from openai import AsyncOpenAI, AsyncStream, OpenAI, Stream
19
+ from openai.lib.streaming.chat import (
20
+ AsyncChatCompletionStreamManager,
21
+ ChatCompletionStreamManager,
22
+ )
19
23
  from pydantic import BaseModel
20
24
 
21
25
  from camel.configs import OPENAI_API_PARAMS, ChatGPTConfig
@@ -40,6 +44,11 @@ if os.environ.get("LANGFUSE_ENABLED", "False").lower() == "true":
40
44
  from langfuse.decorators import observe
41
45
  except ImportError:
42
46
  from camel.utils import observe
47
+ elif os.environ.get("TRACEROOT_ENABLED", "False").lower() == "true":
48
+ try:
49
+ from traceroot import trace as observe # type: ignore[import]
50
+ except ImportError:
51
+ from camel.utils import observe
43
52
  else:
44
53
  from camel.utils import observe
45
54
 
@@ -238,7 +247,11 @@ class OpenAIModel(BaseModelBackend):
238
247
  messages: List[OpenAIMessage],
239
248
  response_format: Optional[Type[BaseModel]] = None,
240
249
  tools: Optional[List[Dict[str, Any]]] = None,
241
- ) -> Union[ChatCompletion, Stream[ChatCompletionChunk]]:
250
+ ) -> Union[
251
+ ChatCompletion,
252
+ Stream[ChatCompletionChunk],
253
+ ChatCompletionStreamManager[BaseModel],
254
+ ]:
242
255
  r"""Runs inference of OpenAI chat completion.
243
256
 
244
257
  Args:
@@ -250,9 +263,12 @@ class OpenAIModel(BaseModelBackend):
250
263
  use for the request.
251
264
 
252
265
  Returns:
253
- Union[ChatCompletion, Stream[ChatCompletionChunk]]:
254
- `ChatCompletion` in the non-stream mode, or
255
- `Stream[ChatCompletionChunk]` in the stream mode.
266
+ Union[ChatCompletion, Stream[ChatCompletionChunk],
267
+ ChatCompletionStreamManager[BaseModel]]:
268
+ `ChatCompletion` in the non-stream mode,
269
+ `Stream[ChatCompletionChunk]`in the stream mode,
270
+ or `ChatCompletionStreamManager[BaseModel]` for
271
+ structured output streaming.
256
272
  """
257
273
 
258
274
  # Update Langfuse trace with current agent session and metadata
@@ -273,10 +289,22 @@ class OpenAIModel(BaseModelBackend):
273
289
  response_format = response_format or self.model_config_dict.get(
274
290
  "response_format", None
275
291
  )
292
+
293
+ # Check if streaming is enabled
294
+ is_streaming = self.model_config_dict.get("stream", False)
295
+
276
296
  if response_format:
277
297
  result: Union[ChatCompletion, Stream[ChatCompletionChunk]] = (
278
298
  self._request_parse(messages, response_format, tools)
279
299
  )
300
+ if is_streaming:
301
+ # Use streaming parse for structured output
302
+ return self._request_stream_parse(
303
+ messages, response_format, tools
304
+ )
305
+ else:
306
+ # Use non-streaming parse for structured output
307
+ return self._request_parse(messages, response_format, tools)
280
308
  else:
281
309
  result = self._request_chat_completion(messages, tools)
282
310
 
@@ -288,7 +316,11 @@ class OpenAIModel(BaseModelBackend):
288
316
  messages: List[OpenAIMessage],
289
317
  response_format: Optional[Type[BaseModel]] = None,
290
318
  tools: Optional[List[Dict[str, Any]]] = None,
291
- ) -> Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
319
+ ) -> Union[
320
+ ChatCompletion,
321
+ AsyncStream[ChatCompletionChunk],
322
+ AsyncChatCompletionStreamManager[BaseModel],
323
+ ]:
292
324
  r"""Runs inference of OpenAI chat completion in async mode.
293
325
 
294
326
  Args:
@@ -300,9 +332,12 @@ class OpenAIModel(BaseModelBackend):
300
332
  use for the request.
301
333
 
302
334
  Returns:
303
- Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
304
- `ChatCompletion` in the non-stream mode, or
305
- `AsyncStream[ChatCompletionChunk]` in the stream mode.
335
+ Union[ChatCompletion, AsyncStream[ChatCompletionChunk],
336
+ AsyncChatCompletionStreamManager[BaseModel]]:
337
+ `ChatCompletion` in the non-stream mode,
338
+ `AsyncStream[ChatCompletionChunk]` in the stream mode, or
339
+ `AsyncChatCompletionStreamManager[BaseModel]` for
340
+ structured output streaming.
306
341
  """
307
342
 
308
343
  # Update Langfuse trace with current agent session and metadata
@@ -323,10 +358,24 @@ class OpenAIModel(BaseModelBackend):
323
358
  response_format = response_format or self.model_config_dict.get(
324
359
  "response_format", None
325
360
  )
361
+
362
+ # Check if streaming is enabled
363
+ is_streaming = self.model_config_dict.get("stream", False)
364
+
326
365
  if response_format:
327
366
  result: Union[
328
367
  ChatCompletion, AsyncStream[ChatCompletionChunk]
329
368
  ] = await self._arequest_parse(messages, response_format, tools)
369
+ if is_streaming:
370
+ # Use streaming parse for structured output
371
+ return await self._arequest_stream_parse(
372
+ messages, response_format, tools
373
+ )
374
+ else:
375
+ # Use non-streaming parse for structured output
376
+ return await self._arequest_parse(
377
+ messages, response_format, tools
378
+ )
330
379
  else:
331
380
  result = await self._arequest_chat_completion(messages, tools)
332
381
 
@@ -422,6 +471,66 @@ class OpenAIModel(BaseModelBackend):
422
471
  **request_config,
423
472
  )
424
473
 
474
+ def _request_stream_parse(
475
+ self,
476
+ messages: List[OpenAIMessage],
477
+ response_format: Type[BaseModel],
478
+ tools: Optional[List[Dict[str, Any]]] = None,
479
+ ) -> ChatCompletionStreamManager[BaseModel]:
480
+ r"""Request streaming structured output parsing.
481
+
482
+ Note: This uses OpenAI's beta streaming API for structured outputs.
483
+ """
484
+ import copy
485
+
486
+ request_config = copy.deepcopy(self.model_config_dict)
487
+
488
+ # Remove stream from config as it's handled by the stream method
489
+ request_config.pop("stream", None)
490
+
491
+ if tools is not None:
492
+ request_config["tools"] = tools
493
+
494
+ request_config = self._sanitize_config(request_config)
495
+
496
+ # Use the beta streaming API for structured outputs
497
+ return self._client.beta.chat.completions.stream(
498
+ messages=messages,
499
+ model=self.model_type,
500
+ response_format=response_format,
501
+ **request_config,
502
+ )
503
+
504
+ async def _arequest_stream_parse(
505
+ self,
506
+ messages: List[OpenAIMessage],
507
+ response_format: Type[BaseModel],
508
+ tools: Optional[List[Dict[str, Any]]] = None,
509
+ ) -> AsyncChatCompletionStreamManager[BaseModel]:
510
+ r"""Request async streaming structured output parsing.
511
+
512
+ Note: This uses OpenAI's beta streaming API for structured outputs.
513
+ """
514
+ import copy
515
+
516
+ request_config = copy.deepcopy(self.model_config_dict)
517
+
518
+ # Remove stream from config as it's handled by the stream method
519
+ request_config.pop("stream", None)
520
+
521
+ if tools is not None:
522
+ request_config["tools"] = tools
523
+
524
+ request_config = self._sanitize_config(request_config)
525
+
526
+ # Use the beta streaming API for structured outputs
527
+ return self._async_client.beta.chat.completions.stream(
528
+ messages=messages,
529
+ model=self.model_type,
530
+ response_format=response_format,
531
+ **request_config,
532
+ )
533
+
425
534
  def check_model_config(self):
426
535
  r"""Check whether the model configuration contains any
427
536
  unexpected arguments to OpenAI API.
@@ -65,7 +65,8 @@ Example valid response:
65
65
  "assignments": [
66
66
  {{"task_id": "task_1", "assignee_id": "node_12345", "dependencies": []}},
67
67
  {{"task_id": "task_2", "assignee_id": "node_67890", "dependencies": ["task_1"]}},
68
- {{"task_id": "task_3", "assignee_id": "node_12345", "dependencies": []}}
68
+ {{"task_id": "task_3", "assignee_id": "node_12345", "dependencies": []}},
69
+ {{"task_id": "task_4", "assignee_id": "node_67890", "dependencies": ["task_1", "task_2"]}}
69
70
  ]
70
71
  }}
71
72
 
@@ -96,6 +97,11 @@ Please keep in mind the task you are going to process, the content of the task t
96
97
  {content}
97
98
  ==============================
98
99
 
100
+ Here is the content of the parent task for you to refer to:
101
+ ==============================
102
+ {parent_task_content}
103
+ ==============================
104
+
99
105
  Here are results of some prerequisite tasks that you can refer to:
100
106
 
101
107
  ==============================
@@ -126,16 +132,22 @@ concluding remarks, explanations, or any other text outside the JSON structure i
126
132
 
127
133
  ROLEPLAY_PROCESS_TASK_PROMPT = TextPrompt(
128
134
  """You need to process the task. It is recommended that tools be actively called when needed.
129
- Here are results of some prerequisite tasks that you can refer to:
135
+
136
+ The content of the task that you need to do is:
130
137
 
131
138
  ==============================
132
- {dependency_task_info}
139
+ {content}
133
140
  ==============================
134
141
 
135
- The content of the task that you need to do is:
142
+ Here is the content of the parent task for you to refer to:
143
+ ==============================
144
+ {parent_task_content}
145
+ ==============================
146
+
147
+ Here are results of some prerequisite tasks that you can refer to:
136
148
 
137
149
  ==============================
138
- {content}
150
+ {dependency_task_info}
139
151
  ==============================
140
152
 
141
153
  Here are some additional information about the task:
@@ -254,3 +266,54 @@ Each subtask should be:
254
266
  - Contain all sequential steps that should be performed by the same worker type
255
267
  - Only separated from other subtasks when parallel execution by different worker types is beneficial
256
268
  """
269
+
270
+ FAILURE_ANALYSIS_PROMPT = TextPrompt(
271
+ """You need to analyze a task failure and decide on the best recovery strategy.
272
+
273
+ **TASK FAILURE DETAILS:**
274
+ Task ID: {task_id}
275
+ Task Content: {task_content}
276
+ Failure Count: {failure_count}/3
277
+ Error Message: {error_message}
278
+ Worker ID: {worker_id}
279
+ Task Depth: {task_depth}
280
+ Additional Info: {additional_info}
281
+
282
+ **AVAILABLE RECOVERY STRATEGIES:**
283
+
284
+ 1. **RETRY**: Attempt the same task again without changes
285
+ - Use for: Network errors, temporary API issues, random failures
286
+ - Avoid for: Fundamental task misunderstanding, capability gaps
287
+
288
+ 2. **REPLAN**: Modify the task content to address the underlying issue
289
+ - Use for: Unclear requirements, insufficient context, correctable errors
290
+ - Provide: Modified task content that addresses the failure cause
291
+
292
+ 3. **DECOMPOSE**: Break the task into smaller, more manageable subtasks
293
+ - Use for: Complex tasks, capability mismatches, persistent failures
294
+ - Consider: Whether the task is too complex for a single worker
295
+
296
+ 4. **CREATE_WORKER**: Create a new worker node to handle the task
297
+ - Use for: Fundamental task misunderstanding, capability gaps
298
+
299
+ **ANALYSIS GUIDELINES:**
300
+
301
+ - **Connection/Network Errors**: Almost always choose RETRY
302
+ - **Model Processing Errors**: Consider REPLAN if the task can be clarified, otherwise DECOMPOSE
303
+ - **Capability Gaps**: Choose DECOMPOSE to break into simpler parts
304
+ - **Ambiguous Requirements**: Choose REPLAN with clearer instructions
305
+ - **High Failure Count**: Lean towards DECOMPOSE rather than repeated retries
306
+ - **Deep Tasks (depth > 2)**: Prefer RETRY or REPLAN over further decomposition
307
+
308
+ **RESPONSE FORMAT:**
309
+ You must return a valid JSON object with these fields:
310
+ - "strategy": one of "retry", "replan", or "decompose"
311
+ - "reasoning": explanation for your choice (1-2 sentences)
312
+ - "modified_task_content": new task content if strategy is "replan", null otherwise
313
+
314
+ **Example Response:**
315
+ {{"strategy": "retry", "reasoning": "The connection error appears to be temporary and network-related, a simple retry should resolve this.", "modified_task_content": null}}
316
+
317
+ **CRITICAL**: Return ONLY the JSON object. No explanations or text outside the JSON structure.
318
+ """
319
+ )
@@ -13,7 +13,6 @@
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
  from __future__ import annotations
15
15
 
16
- import json
17
16
  from typing import Dict, List, Optional
18
17
 
19
18
  from colorama import Fore
@@ -25,6 +24,9 @@ from camel.societies.workforce.prompts import (
25
24
  ROLEPLAY_PROCESS_TASK_PROMPT,
26
25
  ROLEPLAY_SUMMARIZE_PROMPT,
27
26
  )
27
+ from camel.societies.workforce.structured_output_handler import (
28
+ StructuredOutputHandler,
29
+ )
28
30
  from camel.societies.workforce.utils import TaskResult
29
31
  from camel.societies.workforce.worker import Worker
30
32
  from camel.tasks.task import Task, TaskState, is_task_result_insufficient
@@ -48,6 +50,14 @@ class RolePlayingWorker(Worker):
48
50
  (default: :obj:`None`)
49
51
  chat_turn_limit (int): The maximum number of chat turns in the role
50
52
  playing. (default: :obj:`20`)
53
+ use_structured_output_handler (bool, optional): Whether to use the
54
+ structured output handler instead of native structured output.
55
+ When enabled, the workforce will use prompts with structured
56
+ output instructions and regex extraction to parse responses.
57
+ This ensures compatibility with agents that don't reliably
58
+ support native structured output. When disabled, the workforce
59
+ uses the native response_format parameter.
60
+ (default: :obj:`True`)
51
61
  """
52
62
 
53
63
  def __init__(
@@ -59,8 +69,15 @@ class RolePlayingWorker(Worker):
59
69
  user_agent_kwargs: Optional[Dict] = None,
60
70
  summarize_agent_kwargs: Optional[Dict] = None,
61
71
  chat_turn_limit: int = 20,
72
+ use_structured_output_handler: bool = True,
62
73
  ) -> None:
63
74
  super().__init__(description)
75
+ self.use_structured_output_handler = use_structured_output_handler
76
+ self.structured_handler = (
77
+ StructuredOutputHandler()
78
+ if use_structured_output_handler
79
+ else None
80
+ )
64
81
  self.summarize_agent_kwargs = summarize_agent_kwargs
65
82
  summ_sys_msg = BaseMessage.make_assistant_message(
66
83
  role_name="Summarizer",
@@ -104,6 +121,7 @@ class RolePlayingWorker(Worker):
104
121
  dependency_tasks_info = self._get_dep_tasks_info(dependencies)
105
122
  prompt = ROLEPLAY_PROCESS_TASK_PROMPT.format(
106
123
  content=task.content,
124
+ parent_task_content=task.parent.content if task.parent else "",
107
125
  dependency_task_info=dependency_tasks_info,
108
126
  additional_info=task.additional_info,
109
127
  )
@@ -172,13 +190,53 @@ class RolePlayingWorker(Worker):
172
190
  chat_history=chat_history_str,
173
191
  additional_info=task.additional_info,
174
192
  )
175
- response = self.summarize_agent.step(
176
- prompt, response_format=TaskResult
177
- )
178
- result_dict = json.loads(response.msg.content)
179
- task_result = TaskResult(**result_dict)
193
+ if self.use_structured_output_handler and self.structured_handler:
194
+ # Use structured output handler for prompt-based extraction
195
+ enhanced_prompt = (
196
+ self.structured_handler.generate_structured_prompt(
197
+ base_prompt=prompt,
198
+ schema=TaskResult,
199
+ examples=[
200
+ {
201
+ "content": "The assistant successfully completed "
202
+ "the task by...",
203
+ "failed": False,
204
+ }
205
+ ],
206
+ additional_instructions=(
207
+ "Summarize the task execution based "
208
+ "on the chat history, clearly indicating whether "
209
+ "the task succeeded or failed."
210
+ ),
211
+ )
212
+ )
213
+ response = self.summarize_agent.step(enhanced_prompt)
214
+ task_result = self.structured_handler.parse_structured_response(
215
+ response_text=response.msg.content if response.msg else "",
216
+ schema=TaskResult,
217
+ fallback_values={
218
+ "content": "Task summarization failed",
219
+ "failed": True,
220
+ },
221
+ )
222
+ else:
223
+ # Use native structured output if supported
224
+ response = self.summarize_agent.step(
225
+ prompt, response_format=TaskResult
226
+ )
227
+ if response.msg.parsed is None:
228
+ print(
229
+ f"{Fore.RED}Error in summarization: Invalid "
230
+ f"task result{Fore.RESET}"
231
+ )
232
+ task_result = TaskResult(
233
+ content="Failed to generate valid task summary.",
234
+ failed=True,
235
+ )
236
+ else:
237
+ task_result = response.msg.parsed
180
238
 
181
- task.result = task_result.content
239
+ task.result = task_result.content # type: ignore[union-attr]
182
240
 
183
241
  if is_task_result_insufficient(task):
184
242
  print(