Skip to content

Commit 07a9a0f

Browse files
authored
Merge branch 'master' into chore/parallel_tool_call
2 parents c0c3332 + a8085d9 commit 07a9a0f

File tree

10 files changed

+194
-49
lines changed

10 files changed

+194
-49
lines changed

camel/environments/tic_tac_toe.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,9 @@ def evaluate_position_for_x(
413413
Args:
414414
board (List[str]): The current game board as a list of strings.
415415
is_x_turn (bool): True if it's X's turn to move, False otherwise.
416+
depth (int): Current recursion depth. (default: :obj:`0`)
417+
max_depth (int): Maximum recursion depth to prevent stack overflow.
418+
(default: :obj:`10`)
416419
417420
Returns:
418421
float: A float value representing the position evaluation:
@@ -435,14 +438,17 @@ def evaluate_position_for_x(
435438
return 0.5 # Return draw evaluation at max depth
436439

437440
moves = TicTacToeEnv.available_moves(board)
441+
if not moves:
442+
return 0.5
443+
symbol = "X" if is_x_turn else "O"
438444
values = []
439-
# Create a copy of the board to avoid side effects
445+
440446
for move in moves:
441-
board_copy = board.copy()
442-
board_copy[move] = "X" if is_x_turn else "O"
447+
board[move] = symbol
443448
value = TicTacToeEnv.evaluate_position_for_x(
444-
board_copy, not is_x_turn, depth + 1, max_depth
449+
board, not is_x_turn, depth + 1, max_depth
445450
)
451+
board[move] = " " # undo the move
446452
values.append(value)
447453

448454
return max(values) if is_x_turn else min(values)

camel/societies/workforce/events.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class WorkforceEventBase(BaseModel):
2727
"task_created",
2828
"task_assigned",
2929
"task_started",
30+
"task_updated",
3031
"task_completed",
3132
"task_failed",
3233
"worker_created",
@@ -100,6 +101,17 @@ class TaskStartedEvent(WorkforceEventBase):
100101
worker_id: str
101102

102103

104+
class TaskUpdatedEvent(WorkforceEventBase):
105+
event_type: Literal["task_updated"] = "task_updated"
106+
task_id: str
107+
worker_id: Optional[str] = None
108+
update_type: Literal["replan", "reassign", "manual"]
109+
old_value: Optional[str] = None
110+
new_value: Optional[str] = None
111+
parent_task_id: Optional[str] = None
112+
metadata: Optional[Dict[str, Any]] = None
113+
114+
103115
class TaskCompletedEvent(WorkforceEventBase):
104116
event_type: Literal["task_completed"] = "task_completed"
105117
task_id: str
@@ -136,6 +148,7 @@ class QueueStatusEvent(WorkforceEventBase):
136148
TaskCreatedEvent,
137149
TaskAssignedEvent,
138150
TaskStartedEvent,
151+
TaskUpdatedEvent,
139152
TaskCompletedEvent,
140153
TaskFailedEvent,
141154
WorkerCreatedEvent,

camel/societies/workforce/workforce.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
TaskDecomposedEvent,
103103
TaskFailedEvent,
104104
TaskStartedEvent,
105+
TaskUpdatedEvent,
105106
WorkerCreatedEvent,
106107
)
107108

@@ -1673,8 +1674,30 @@ async def _apply_recovery_strategy(
16731674
elif strategy == RecoveryStrategy.REPLAN:
16741675
# Modify the task content and retry
16751676
if recovery_decision.modified_task_content:
1676-
task.content = recovery_decision.modified_task_content
1677-
logger.info(f"Task {task.id} content modified for replan")
1677+
old_content = task.content
1678+
new_content = recovery_decision.modified_task_content
1679+
1680+
task.content = new_content
1681+
logger.info(
1682+
f"Task {task.id} content modified for replan, "
1683+
f"new_content: {new_content}"
1684+
)
1685+
1686+
task_updated_event = TaskUpdatedEvent(
1687+
task_id=task.id,
1688+
parent_task_id=task.parent.id if task.parent else None,
1689+
worker_id=task.assigned_worker_id,
1690+
update_type="replan",
1691+
old_value=old_content,
1692+
new_value=new_content,
1693+
metadata={
1694+
"quality_score": recovery_decision.quality_score,
1695+
"reasoning": recovery_decision.reasoning,
1696+
"issues": recovery_decision.issues,
1697+
},
1698+
)
1699+
for cb in self._callbacks:
1700+
cb.log_task_updated(task_updated_event)
16781701

16791702
# Repost the modified task to the same worker
16801703
await self._post_task(task, original_assignee)
@@ -1717,6 +1740,22 @@ async def _apply_recovery_strategy(
17171740
f"{new_worker}"
17181741
)
17191742

1743+
task_updated_event = TaskUpdatedEvent(
1744+
task_id=task.id,
1745+
parent_task_id=task.parent.id if task.parent else None,
1746+
worker_id=task.assigned_worker_id,
1747+
update_type="reassign",
1748+
old_value=old_worker,
1749+
new_value=new_worker,
1750+
metadata={
1751+
"quality_score": recovery_decision.quality_score,
1752+
"reasoning": recovery_decision.reasoning,
1753+
"issues": recovery_decision.issues,
1754+
},
1755+
)
1756+
for cb in self._callbacks:
1757+
cb.log_task_updated(task_updated_event)
1758+
17201759
elif strategy == RecoveryStrategy.DECOMPOSE:
17211760
# Decompose the task into subtasks
17221761
reason = (
@@ -2056,8 +2095,21 @@ def modify_task_content(self, task_id: str, new_content: str) -> bool:
20562095

20572096
for task in self._pending_tasks:
20582097
if task.id == task_id:
2098+
old_content = task.content
20592099
task.content = new_content
20602100
logger.info(f"Task {task_id} content modified.")
2101+
2102+
task_updated_event = TaskUpdatedEvent(
2103+
task_id=task.id,
2104+
parent_task_id=task.parent.id if task.parent else None,
2105+
worker_id=task.assigned_worker_id,
2106+
update_type="manual",
2107+
old_value=old_content,
2108+
new_value=new_content,
2109+
)
2110+
for cb in self._callbacks:
2111+
cb.log_task_updated(task_updated_event)
2112+
20612113
return True
20622114
logger.warning(f"Task {task_id} not found in pending tasks.")
20632115
return False

camel/societies/workforce/workforce_callback.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
TaskDecomposedEvent,
2828
TaskFailedEvent,
2929
TaskStartedEvent,
30+
TaskUpdatedEvent,
3031
WorkerCreatedEvent,
3132
WorkerDeletedEvent,
3233
)
@@ -82,6 +83,10 @@ def log_task_assigned(self, event: TaskAssignedEvent) -> None:
8283
def log_task_started(self, event: TaskStartedEvent) -> None:
8384
pass
8485

86+
@abstractmethod
87+
def log_task_updated(self, event: TaskUpdatedEvent) -> None:
88+
pass
89+
8590
@abstractmethod
8691
def log_task_completed(self, event: TaskCompletedEvent) -> None:
8792
pass

camel/societies/workforce/workforce_logger.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
TaskDecomposedEvent,
2727
TaskFailedEvent,
2828
TaskStartedEvent,
29+
TaskUpdatedEvent,
2930
WorkerCreatedEvent,
3031
WorkerDeletedEvent,
3132
)
@@ -157,6 +158,21 @@ def log_task_started(
157158
if event.task_id in self._task_hierarchy:
158159
self._task_hierarchy[event.task_id]['status'] = 'processing'
159160

161+
def log_task_updated(self, event: TaskUpdatedEvent) -> None:
162+
r"""Logs updates made to a task."""
163+
self._log_event(
164+
event_type=event.event_type,
165+
task_id=event.task_id,
166+
worker_id=event.worker_id,
167+
update_type=event.update_type,
168+
old_value=event.old_value,
169+
new_value=event.new_value,
170+
parent_task_id=event.parent_task_id,
171+
metadata=event.metadata or {},
172+
)
173+
if event.task_id in self._task_hierarchy:
174+
self._task_hierarchy[event.task_id]['status'] = 'updated'
175+
160176
def log_task_completed(self, event: TaskCompletedEvent) -> None:
161177
r"""Logs the successful completion of a task."""
162178
self._log_event(

camel/toolkits/terminal_toolkit/utils.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,17 @@ def setup_initial_env_with_uv(
297297
) -> bool:
298298
r"""Set up initial environment using uv."""
299299
try:
300+
if platform.system() == 'Windows':
301+
python_path = os.path.join(env_path, "Scripts", "python.exe")
302+
else:
303+
python_path = os.path.join(env_path, "bin", "python")
304+
305+
if os.path.exists(python_path):
306+
if update_callback:
307+
update_callback(
308+
"[UV] Environment already exists, skipping creation\n"
309+
)
310+
return True
300311
# Create virtual environment with Python 3.10 using uv
301312
subprocess.run(
302313
[uv_path, "venv", "--python", "3.10", env_path],
@@ -306,12 +317,6 @@ def setup_initial_env_with_uv(
306317
timeout=300,
307318
)
308319

309-
# Get the python path from the new environment
310-
if platform.system() == 'Windows':
311-
python_path = os.path.join(env_path, "Scripts", "python.exe")
312-
else:
313-
python_path = os.path.join(env_path, "bin", "python")
314-
315320
# Install essential packages using uv
316321
essential_packages = [
317322
"pip",
@@ -356,6 +361,20 @@ def setup_initial_env_with_venv(
356361
) -> bool:
357362
r"""Set up initial environment using standard venv."""
358363
try:
364+
# Get pip path
365+
if platform.system() == 'Windows':
366+
pip_path = os.path.join(env_path, "Scripts", "pip.exe")
367+
else:
368+
pip_path = os.path.join(env_path, "bin", "pip")
369+
370+
# Check if environment already exists
371+
if os.path.exists(pip_path):
372+
if update_callback:
373+
update_callback(
374+
"Environment already exists, skipping creation\n"
375+
)
376+
return True
377+
359378
# Create virtual environment with system Python
360379
try:
361380
venv.create(
@@ -374,12 +393,6 @@ def setup_initial_env_with_venv(
374393
symlinks=False,
375394
)
376395

377-
# Get pip path
378-
if platform.system() == 'Windows':
379-
pip_path = os.path.join(env_path, "Scripts", "pip.exe")
380-
else:
381-
pip_path = os.path.join(env_path, "bin", "pip")
382-
383396
# Upgrade pip and install essential packages
384397
essential_packages = [
385398
"pip",

docs/mintlify/reference/camel.societies.workforce.workforce_callback.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ def log_task_assigned(self, event: TaskAssignedEvent):
6262
def log_task_started(self, event: TaskStartedEvent):
6363
```
6464

65+
<a id="camel.societies.workforce.workforce_callback.WorkforceCallback.log_task_updated"></a>
66+
67+
### log_task_updated
68+
69+
```python
70+
def log_task_updated(self, event: TaskUpdatedEvent):
71+
```
72+
6573
<a id="camel.societies.workforce.workforce_callback.WorkforceCallback.log_task_completed"></a>
6674

6775
### log_task_completed

docs/mintlify/reference/camel.societies.workforce.workforce_logger.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@ def log_task_started(self, event: TaskStartedEvent):
8888

8989
Logs when a worker starts processing a task.
9090

91+
<a id="camel.societies.workforce.workforce_logger.WorkforceLogger.log_task_updated"></a>
92+
93+
### log_task_updated
94+
95+
```python
96+
def log_task_updated(self, event: TaskUpdatedEvent):
97+
```
98+
99+
Logs updates made to a task.
100+
91101
<a id="camel.societies.workforce.workforce_logger.WorkforceLogger.log_task_completed"></a>
92102

93103
### log_task_completed

examples/workforce/workforce_callbacks_example.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
TaskDecomposedEvent,
3535
TaskFailedEvent,
3636
TaskStartedEvent,
37+
TaskUpdatedEvent,
3738
WorkerCreatedEvent,
3839
WorkerDeletedEvent,
3940
)
@@ -78,6 +79,12 @@ def log_task_started(self, event: TaskStartedEvent) -> None:
7879
f"worker={event.worker_id}"
7980
)
8081

82+
def log_task_updated(self, event: TaskUpdatedEvent) -> None:
83+
print(
84+
f"[PrintCallback] task_updated: task={event.task_id}, "
85+
f"worker={event.worker_id}"
86+
)
87+
8188
def log_task_completed(self, event: TaskCompletedEvent) -> None:
8289
print(
8390
f"[PrintCallback] task_completed: task={event.task_id}, "

0 commit comments

Comments
 (0)