camel-ai 0.2.40__py3-none-any.whl → 0.2.42__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.
- camel/__init__.py +1 -1
- camel/agents/chat_agent.py +30 -10
- camel/datasets/few_shot_generator.py +14 -6
- camel/environments/single_step.py +28 -11
- camel/memories/blocks/chat_history_block.py +46 -3
- camel/models/vllm_model.py +16 -0
- camel/toolkits/terminal_toolkit.py +729 -115
- camel/types/enums.py +1 -0
- {camel_ai-0.2.40.dist-info → camel_ai-0.2.42.dist-info}/METADATA +2 -1
- {camel_ai-0.2.40.dist-info → camel_ai-0.2.42.dist-info}/RECORD +12 -12
- {camel_ai-0.2.40.dist-info → camel_ai-0.2.42.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.40.dist-info → camel_ai-0.2.42.dist-info}/licenses/LICENSE +0 -0
camel/__init__.py
CHANGED
camel/agents/chat_agent.py
CHANGED
|
@@ -327,7 +327,10 @@ class ChatAgent(BaseAgent):
|
|
|
327
327
|
return False
|
|
328
328
|
|
|
329
329
|
def update_memory(
|
|
330
|
-
self,
|
|
330
|
+
self,
|
|
331
|
+
message: BaseMessage,
|
|
332
|
+
role: OpenAIBackendRole,
|
|
333
|
+
timestamp: Optional[float] = None,
|
|
331
334
|
) -> None:
|
|
332
335
|
r"""Updates the agent memory with a new message.
|
|
333
336
|
|
|
@@ -335,12 +338,19 @@ class ChatAgent(BaseAgent):
|
|
|
335
338
|
message (BaseMessage): The new message to add to the stored
|
|
336
339
|
messages.
|
|
337
340
|
role (OpenAIBackendRole): The backend role type.
|
|
341
|
+
timestamp (Optional[float], optional): Custom timestamp for the
|
|
342
|
+
memory record. If None, current timestamp will be used.
|
|
343
|
+
(default: :obj:`None`)
|
|
338
344
|
"""
|
|
345
|
+
from datetime import timezone
|
|
346
|
+
|
|
339
347
|
self.memory.write_record(
|
|
340
348
|
MemoryRecord(
|
|
341
349
|
message=message,
|
|
342
350
|
role_at_backend=role,
|
|
343
|
-
timestamp=
|
|
351
|
+
timestamp=timestamp
|
|
352
|
+
if timestamp is not None
|
|
353
|
+
else datetime.now(timezone.utc).timestamp(),
|
|
344
354
|
agent_id=self.agent_id,
|
|
345
355
|
)
|
|
346
356
|
)
|
|
@@ -603,9 +613,6 @@ class ChatAgent(BaseAgent):
|
|
|
603
613
|
self._get_full_tool_schemas(),
|
|
604
614
|
)
|
|
605
615
|
|
|
606
|
-
if self.single_iteration:
|
|
607
|
-
break
|
|
608
|
-
|
|
609
616
|
if tool_call_requests := response.tool_call_requests:
|
|
610
617
|
# Process all tool calls
|
|
611
618
|
for tool_call_request in tool_call_requests:
|
|
@@ -625,6 +632,9 @@ class ChatAgent(BaseAgent):
|
|
|
625
632
|
if external_tool_call_requests:
|
|
626
633
|
break
|
|
627
634
|
|
|
635
|
+
if self.single_iteration:
|
|
636
|
+
break
|
|
637
|
+
|
|
628
638
|
# If we're still here, continue the loop
|
|
629
639
|
continue
|
|
630
640
|
|
|
@@ -695,9 +705,6 @@ class ChatAgent(BaseAgent):
|
|
|
695
705
|
self._get_full_tool_schemas(),
|
|
696
706
|
)
|
|
697
707
|
|
|
698
|
-
if self.single_iteration:
|
|
699
|
-
break
|
|
700
|
-
|
|
701
708
|
if tool_call_requests := response.tool_call_requests:
|
|
702
709
|
# Process all tool calls
|
|
703
710
|
for tool_call_request in tool_call_requests:
|
|
@@ -718,6 +725,9 @@ class ChatAgent(BaseAgent):
|
|
|
718
725
|
if external_tool_call_requests:
|
|
719
726
|
break
|
|
720
727
|
|
|
728
|
+
if self.single_iteration:
|
|
729
|
+
break
|
|
730
|
+
|
|
721
731
|
# If we're still here, continue the loop
|
|
722
732
|
continue
|
|
723
733
|
|
|
@@ -1331,8 +1341,18 @@ class ChatAgent(BaseAgent):
|
|
|
1331
1341
|
tool_call_id=tool_call_id,
|
|
1332
1342
|
)
|
|
1333
1343
|
|
|
1334
|
-
|
|
1335
|
-
|
|
1344
|
+
# Use slightly different timestamps to ensure correct ordering
|
|
1345
|
+
# This ensures the assistant message (tool call) always appears before
|
|
1346
|
+
# the function message (tool result) in the conversation context
|
|
1347
|
+
current_time = datetime.now().timestamp()
|
|
1348
|
+
self.update_memory(
|
|
1349
|
+
assist_msg, OpenAIBackendRole.ASSISTANT, timestamp=current_time
|
|
1350
|
+
)
|
|
1351
|
+
self.update_memory(
|
|
1352
|
+
func_msg,
|
|
1353
|
+
OpenAIBackendRole.FUNCTION,
|
|
1354
|
+
timestamp=current_time + 0.001,
|
|
1355
|
+
)
|
|
1336
1356
|
|
|
1337
1357
|
# Record information about this tool call
|
|
1338
1358
|
tool_record = ToolCallingRecord(
|
|
@@ -201,19 +201,27 @@ class FewShotGenerator(BaseGenerator):
|
|
|
201
201
|
raise TypeError(f"Rationale {rationale} is not a string.")
|
|
202
202
|
|
|
203
203
|
try:
|
|
204
|
-
verifier_response = await
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
verifier_response = await asyncio.wait_for(
|
|
205
|
+
self.verifier.verify(
|
|
206
|
+
solution=rationale,
|
|
207
|
+
reference_answer=None,
|
|
208
|
+
),
|
|
209
|
+
timeout=180,
|
|
207
210
|
)
|
|
208
211
|
if not verifier_response or not verifier_response.result:
|
|
209
212
|
raise ValueError(
|
|
210
213
|
"Verifier unsuccessful, response: "
|
|
211
214
|
f"{verifier_response}"
|
|
212
215
|
)
|
|
213
|
-
except (ValueError, AttributeError) as e:
|
|
216
|
+
except (ValueError, AttributeError, asyncio.TimeoutError) as e:
|
|
217
|
+
error_msg = (
|
|
218
|
+
"Verifier timeout"
|
|
219
|
+
if isinstance(e, asyncio.TimeoutError)
|
|
220
|
+
else f"Verifier issue: {e}"
|
|
221
|
+
)
|
|
214
222
|
logger.warning(
|
|
215
|
-
f"
|
|
216
|
-
f"
|
|
223
|
+
f"{error_msg}, retrying... "
|
|
224
|
+
f"({retries + 1}/{max_retries})"
|
|
217
225
|
)
|
|
218
226
|
retries += 1
|
|
219
227
|
continue
|
|
@@ -224,7 +224,7 @@ class SingleStepEnv:
|
|
|
224
224
|
raise TypeError(f"Unsupported dataset type: {type(self.dataset)}")
|
|
225
225
|
|
|
226
226
|
async def step(
|
|
227
|
-
self, action: Union[Action, List[Action], str]
|
|
227
|
+
self, action: Union[Action, List[Action], str, Dict[int, str]]
|
|
228
228
|
) -> Union[
|
|
229
229
|
Tuple[Observation, float, bool, Dict[str, Any]],
|
|
230
230
|
List[Tuple[Observation, float, bool, Dict[str, Any]]],
|
|
@@ -242,13 +242,15 @@ class SingleStepEnv:
|
|
|
242
242
|
the observation will not change.
|
|
243
243
|
|
|
244
244
|
Args:
|
|
245
|
-
action (Union[Action, List[Action], str]):
|
|
245
|
+
action (Union[Action, List[Action], str, Dict[int, str]]):
|
|
246
246
|
The action(s) taken by the agent,
|
|
247
247
|
which should contain the response(s)
|
|
248
248
|
to the observation(s). Can be:
|
|
249
249
|
- A single `Action` object (for batch size 1),
|
|
250
250
|
- A list of `Action` objects (for batched evaluation),
|
|
251
251
|
- A raw string (only allowed when batch size is 1).
|
|
252
|
+
- A dict that maps indices to their `llm_response`
|
|
253
|
+
(for batched evaluation)
|
|
252
254
|
|
|
253
255
|
Returns:
|
|
254
256
|
Union[Tuple[Observation, float, bool, Dict[str, Any]], List[...]]:
|
|
@@ -293,6 +295,7 @@ class SingleStepEnv:
|
|
|
293
295
|
f"total batch size ({self.current_batch_size})"
|
|
294
296
|
)
|
|
295
297
|
|
|
298
|
+
indices = [act.index for act in actions]
|
|
296
299
|
proposed_solutions = [act.llm_response for act in actions]
|
|
297
300
|
ground_truths: List[str] = []
|
|
298
301
|
for idx in indices:
|
|
@@ -334,21 +337,22 @@ class SingleStepEnv:
|
|
|
334
337
|
).as_tuple()
|
|
335
338
|
for i in range(len(actions))
|
|
336
339
|
]
|
|
340
|
+
|
|
337
341
|
for _, idx in enumerate(indices):
|
|
338
342
|
self._states_done[idx] = True
|
|
339
343
|
|
|
340
344
|
return step_results[0] if len(step_results) == 1 else step_results
|
|
341
345
|
|
|
342
346
|
def _normalize_actions(
|
|
343
|
-
self, action: Union[Action, List[Action], str]
|
|
347
|
+
self, action: Union[Action, List[Action], str, Dict[int, str]]
|
|
344
348
|
) -> List[Action]:
|
|
345
349
|
r"""Normalize the user-provided action(s) into a validated list
|
|
346
350
|
of `Action` objects.
|
|
347
351
|
|
|
348
352
|
This method handles flexibility in input format by converting
|
|
349
|
-
raw strings (only allowed when batch size is 1) and
|
|
350
|
-
all necessary structure and integrity checks on
|
|
351
|
-
(e.g., index bounds, duplicates).
|
|
353
|
+
raw strings (only allowed when batch size is 1) and dictionaries,
|
|
354
|
+
ensuring all necessary structure and integrity checks on
|
|
355
|
+
actions (e.g., index bounds, duplicates).
|
|
352
356
|
|
|
353
357
|
Args:
|
|
354
358
|
action (Union[Action, List[Action], str]):
|
|
@@ -357,6 +361,7 @@ class SingleStepEnv:
|
|
|
357
361
|
- A list of `Action` objects.
|
|
358
362
|
- A raw string (if `batch_size == 1`), auto-wrapped
|
|
359
363
|
in an `Action`.
|
|
364
|
+
- A dict mapping int indices to str responses
|
|
360
365
|
|
|
361
366
|
Returns:
|
|
362
367
|
List[Action]: A list of validated `Action` instances
|
|
@@ -368,8 +373,9 @@ class SingleStepEnv:
|
|
|
368
373
|
- Action list is empty,
|
|
369
374
|
- Index mismatches expected values
|
|
370
375
|
(e.g., 0 for batch size 1),
|
|
371
|
-
- Wrong structure is used
|
|
372
|
-
|
|
376
|
+
- Wrong structure is used (e.g.,
|
|
377
|
+
string used with batch size > 1,
|
|
378
|
+
dict used with batch size == 1).
|
|
373
379
|
TypeError: If the action is of an unsupported type.
|
|
374
380
|
"""
|
|
375
381
|
|
|
@@ -380,9 +386,20 @@ class SingleStepEnv:
|
|
|
380
386
|
" when batch_size == 1"
|
|
381
387
|
)
|
|
382
388
|
logger.warning("Auto-converting from str to Action", stacklevel=2)
|
|
383
|
-
|
|
389
|
+
actions = [Action(index=0, llm_response=action)]
|
|
390
|
+
|
|
391
|
+
elif isinstance(action, dict):
|
|
392
|
+
if not all(isinstance(k, int) for k in action.keys()):
|
|
393
|
+
raise ValueError("All dictionary keys must be integers")
|
|
384
394
|
|
|
385
|
-
|
|
395
|
+
if self.current_batch_size == 1 and list(action.keys()) != [0]:
|
|
396
|
+
raise ValueError(
|
|
397
|
+
"For batch_size=1, dict input must have exactly one key: 0"
|
|
398
|
+
)
|
|
399
|
+
actions = [
|
|
400
|
+
Action(index=k, llm_response=v) for k, v in action.items()
|
|
401
|
+
]
|
|
402
|
+
elif isinstance(action, Action):
|
|
386
403
|
actions = [action]
|
|
387
404
|
elif isinstance(action, list):
|
|
388
405
|
if not action:
|
|
@@ -397,7 +414,7 @@ class SingleStepEnv:
|
|
|
397
414
|
|
|
398
415
|
if self.current_batch_size == 1 and len(actions) != 1:
|
|
399
416
|
raise ValueError(
|
|
400
|
-
"For batch_size=1, expect a single Action or a "
|
|
417
|
+
"For batch_size=1, expect a single Action, a dictionary or a "
|
|
401
418
|
"list containing exactly one Action"
|
|
402
419
|
)
|
|
403
420
|
|
|
@@ -74,9 +74,52 @@ class ChatHistoryBlock(MemoryBlock):
|
|
|
74
74
|
return list()
|
|
75
75
|
|
|
76
76
|
chat_records: List[MemoryRecord] = []
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
if window_size is not None and window_size >= 0:
|
|
78
|
+
# Initial preserved index: Keep first message
|
|
79
|
+
# if it's SYSTEM/DEVELOPER (index 0)
|
|
80
|
+
start_index = (
|
|
81
|
+
1
|
|
82
|
+
if (
|
|
83
|
+
record_dicts
|
|
84
|
+
and record_dicts[0]['role_at_backend']
|
|
85
|
+
in {OpenAIBackendRole.SYSTEM, OpenAIBackendRole.DEVELOPER}
|
|
86
|
+
)
|
|
87
|
+
else 0
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
Message Processing Logic:
|
|
92
|
+
1. Preserve first system/developer message (if needed)
|
|
93
|
+
2. Keep latest window_size messages from the rest
|
|
94
|
+
|
|
95
|
+
Examples:
|
|
96
|
+
- Case 1: First message is SYSTEM, total 5 messages, window_size=2
|
|
97
|
+
Input: [system_msg, user_msg1, user_msg2, user_msg3, user_msg4]
|
|
98
|
+
Result: [system_msg] + [user_msg3, user_msg4]
|
|
99
|
+
|
|
100
|
+
- Case 2: First message is USER, total 5 messages, window_size=3
|
|
101
|
+
Input: [user_msg1, user_msg2, user_msg3, user_msg4, , user_msg5]
|
|
102
|
+
Result: [user_msg3, user_msg4, , user_msg5]
|
|
103
|
+
"""
|
|
104
|
+
preserved_messages = record_dicts[
|
|
105
|
+
:start_index
|
|
106
|
+
] # Preserve system message (if exists)
|
|
107
|
+
sliding_messages = record_dicts[
|
|
108
|
+
start_index:
|
|
109
|
+
] # Messages to be truncated
|
|
110
|
+
|
|
111
|
+
# Take last window_size messages (if exceeds limit)
|
|
112
|
+
truncated_messages = sliding_messages[-window_size:]
|
|
113
|
+
|
|
114
|
+
# Combine preserved messages with truncated window messages
|
|
115
|
+
final_records = preserved_messages + truncated_messages
|
|
116
|
+
else:
|
|
117
|
+
# Return full records when no window restriction
|
|
118
|
+
final_records = record_dicts
|
|
119
|
+
|
|
120
|
+
chat_records = [
|
|
121
|
+
MemoryRecord.from_dict(record) for record in final_records
|
|
122
|
+
]
|
|
80
123
|
|
|
81
124
|
# We assume that, in the chat history memory, the closer the record is
|
|
82
125
|
# to the current message, the more score it will be.
|
camel/models/vllm_model.py
CHANGED
|
@@ -162,6 +162,14 @@ class VLLMModel(BaseModelBackend):
|
|
|
162
162
|
if response_format:
|
|
163
163
|
kwargs["response_format"] = {"type": "json_object"}
|
|
164
164
|
|
|
165
|
+
# Remove additionalProperties from each tool's function parameters
|
|
166
|
+
if tools and "tools" in kwargs:
|
|
167
|
+
for tool in kwargs["tools"]:
|
|
168
|
+
if "function" in tool and "parameters" in tool["function"]:
|
|
169
|
+
tool["function"]["parameters"].pop(
|
|
170
|
+
"additionalProperties", None
|
|
171
|
+
)
|
|
172
|
+
|
|
165
173
|
response = await self._async_client.chat.completions.create(
|
|
166
174
|
messages=messages,
|
|
167
175
|
model=self.model_type,
|
|
@@ -197,6 +205,14 @@ class VLLMModel(BaseModelBackend):
|
|
|
197
205
|
if response_format:
|
|
198
206
|
kwargs["response_format"] = {"type": "json_object"}
|
|
199
207
|
|
|
208
|
+
# Remove additionalProperties from each tool's function parameters
|
|
209
|
+
if tools and "tools" in kwargs:
|
|
210
|
+
for tool in kwargs["tools"]:
|
|
211
|
+
if "function" in tool and "parameters" in tool["function"]:
|
|
212
|
+
tool["function"]["parameters"].pop(
|
|
213
|
+
"additionalProperties", None
|
|
214
|
+
)
|
|
215
|
+
|
|
200
216
|
response = self._client.chat.completions.create(
|
|
201
217
|
messages=messages,
|
|
202
218
|
model=self.model_type,
|