camel-ai 0.2.62__py3-none-any.whl → 0.2.65__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 +95 -24
- camel/agents/mcp_agent.py +5 -1
- camel/benchmarks/mock_website/README.md +96 -0
- camel/benchmarks/mock_website/mock_web.py +299 -0
- camel/benchmarks/mock_website/requirements.txt +3 -0
- camel/benchmarks/mock_website/shopping_mall/app.py +465 -0
- camel/benchmarks/mock_website/task.json +104 -0
- camel/configs/__init__.py +3 -0
- camel/configs/crynux_config.py +94 -0
- camel/datasets/models.py +1 -1
- camel/datasets/static_dataset.py +6 -0
- camel/interpreters/base.py +14 -1
- camel/interpreters/docker/Dockerfile +63 -7
- camel/interpreters/docker_interpreter.py +65 -7
- camel/interpreters/e2b_interpreter.py +23 -8
- camel/interpreters/internal_python_interpreter.py +30 -2
- camel/interpreters/ipython_interpreter.py +21 -3
- camel/interpreters/subprocess_interpreter.py +34 -2
- camel/memories/records.py +5 -3
- camel/models/__init__.py +2 -0
- camel/models/azure_openai_model.py +101 -25
- camel/models/cohere_model.py +65 -0
- camel/models/crynux_model.py +94 -0
- camel/models/deepseek_model.py +43 -1
- camel/models/gemini_model.py +50 -4
- camel/models/litellm_model.py +38 -0
- camel/models/mistral_model.py +66 -0
- camel/models/model_factory.py +10 -1
- camel/models/openai_compatible_model.py +81 -17
- camel/models/openai_model.py +87 -16
- camel/models/reka_model.py +69 -0
- camel/models/samba_model.py +69 -2
- camel/models/sglang_model.py +74 -2
- camel/models/watsonx_model.py +62 -0
- camel/societies/workforce/role_playing_worker.py +11 -3
- camel/societies/workforce/single_agent_worker.py +31 -1
- camel/societies/workforce/utils.py +51 -0
- camel/societies/workforce/workforce.py +409 -7
- camel/storages/__init__.py +2 -0
- camel/storages/vectordb_storages/__init__.py +2 -0
- camel/storages/vectordb_storages/weaviate.py +714 -0
- camel/tasks/task.py +27 -10
- camel/toolkits/async_browser_toolkit.py +97 -54
- camel/toolkits/browser_toolkit.py +65 -18
- camel/toolkits/code_execution.py +37 -8
- camel/toolkits/function_tool.py +2 -2
- camel/toolkits/mcp_toolkit.py +13 -2
- camel/toolkits/playwright_mcp_toolkit.py +16 -3
- camel/toolkits/task_planning_toolkit.py +134 -0
- camel/types/enums.py +61 -2
- camel/types/unified_model_type.py +5 -0
- camel/utils/__init__.py +16 -0
- camel/utils/langfuse.py +258 -0
- camel/utils/mcp_client.py +84 -17
- {camel_ai-0.2.62.dist-info → camel_ai-0.2.65.dist-info}/METADATA +9 -12
- {camel_ai-0.2.62.dist-info → camel_ai-0.2.65.dist-info}/RECORD +59 -49
- {camel_ai-0.2.62.dist-info → camel_ai-0.2.65.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.62.dist-info → camel_ai-0.2.65.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import os
|
|
18
|
+
import sys # Import the sys module
|
|
19
|
+
from logging.handlers import RotatingFileHandler
|
|
20
|
+
from typing import Any, Dict, List
|
|
21
|
+
|
|
22
|
+
app = None
|
|
23
|
+
products: List[Dict[str, Any]] = []
|
|
24
|
+
products_by_id: Dict[int, Dict[str, Any]] = {}
|
|
25
|
+
cart: List[Dict[str, Any]] = []
|
|
26
|
+
ACTION_COUNT: int = 0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def load_products(file_path: str = 'products.json') -> None:
|
|
30
|
+
global products, products_by_id
|
|
31
|
+
try:
|
|
32
|
+
# The products.json is expected to be
|
|
33
|
+
# in the same directory as this app.py
|
|
34
|
+
# or given by mock_web.py
|
|
35
|
+
script_dir = os.path.dirname(__file__)
|
|
36
|
+
abs_file_path = os.path.join(script_dir, file_path)
|
|
37
|
+
with open(abs_file_path, 'r') as f:
|
|
38
|
+
products = json.load(f)
|
|
39
|
+
products_by_id = {product['id']: product for product in products}
|
|
40
|
+
except FileNotFoundError:
|
|
41
|
+
sys.stderr.write(f"Error: {file_path} not found.\n")
|
|
42
|
+
products = []
|
|
43
|
+
products_by_id = {}
|
|
44
|
+
except json.JSONDecodeError:
|
|
45
|
+
sys.stderr.write(f"Error: Could not decode JSON from {file_path}.\n")
|
|
46
|
+
products = []
|
|
47
|
+
products_by_id = {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# --- Logging Setup ---
|
|
51
|
+
def setup_logging(application):
|
|
52
|
+
log_formatter = logging.Formatter(
|
|
53
|
+
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
|
|
54
|
+
)
|
|
55
|
+
# Log to a file in the same directory as this script
|
|
56
|
+
script_dir = os.path.dirname(__file__)
|
|
57
|
+
log_file = os.path.join(script_dir, 'app.log')
|
|
58
|
+
|
|
59
|
+
# File Handler
|
|
60
|
+
file_handler = RotatingFileHandler(
|
|
61
|
+
log_file, maxBytes=1024 * 1024 * 10, backupCount=5
|
|
62
|
+
) # 10MB per file, 5 backups
|
|
63
|
+
file_handler.setFormatter(log_formatter)
|
|
64
|
+
file_handler.setLevel(logging.INFO)
|
|
65
|
+
|
|
66
|
+
# Stream Handler (for console output)
|
|
67
|
+
stream_handler = logging.StreamHandler(
|
|
68
|
+
sys.stdout
|
|
69
|
+
) # Explicitly set to stdout
|
|
70
|
+
stream_handler.setFormatter(log_formatter)
|
|
71
|
+
stream_handler.setLevel(logging.INFO)
|
|
72
|
+
|
|
73
|
+
application.logger.addHandler(file_handler)
|
|
74
|
+
application.logger.addHandler(stream_handler)
|
|
75
|
+
application.logger.setLevel(logging.INFO)
|
|
76
|
+
application.logger.info(
|
|
77
|
+
f"Logging setup complete. Logs will be saved to {log_file}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# --- End Logging Setup ---
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# --- Task Mode Helper ---
|
|
85
|
+
def check_task_completion(current_cart_raw, ground_truth_spec):
|
|
86
|
+
if not ground_truth_spec: # No ground truth defined
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
# Group current cart by product ID and count quantity
|
|
90
|
+
current_cart_grouped = {}
|
|
91
|
+
for item in current_cart_raw:
|
|
92
|
+
pid = item['id']
|
|
93
|
+
current_cart_grouped[pid] = current_cart_grouped.get(pid, 0) + 1
|
|
94
|
+
|
|
95
|
+
# Convert ground truth spec to a comparable dictionary {product_id:
|
|
96
|
+
# quantity}
|
|
97
|
+
ground_truth_dict = {}
|
|
98
|
+
for item_spec in ground_truth_spec:
|
|
99
|
+
ground_truth_dict[item_spec['id']] = item_spec['quantity']
|
|
100
|
+
|
|
101
|
+
# Check if current cart exactly matches ground truth
|
|
102
|
+
# 1. Same number of unique product types
|
|
103
|
+
if len(current_cart_grouped) != len(ground_truth_dict):
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
# 2. Each product in current cart matches ground truth quantity, and all
|
|
107
|
+
# ground truth items are present
|
|
108
|
+
for pid, qty_spec in ground_truth_dict.items():
|
|
109
|
+
if (
|
|
110
|
+
pid not in current_cart_grouped
|
|
111
|
+
or current_cart_grouped[pid] != qty_spec
|
|
112
|
+
):
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
# Ensure no extra items in current_cart_grouped that are not in
|
|
116
|
+
# ground_truth_dict (already covered by length check if all ground truth
|
|
117
|
+
# items are found)
|
|
118
|
+
# For robustness, explicitly check this too:
|
|
119
|
+
for pid_current in current_cart_grouped.keys():
|
|
120
|
+
if pid_current not in ground_truth_dict:
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
return True
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# --- End Task Mode Helper ---
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# --- Task Completion Helper ---
|
|
130
|
+
def _trigger_task_completion_check():
|
|
131
|
+
r"""Checks for task completion if in task mode and not already signaled."""
|
|
132
|
+
# Uses global `app` and `cart`
|
|
133
|
+
if app.config.get('TASK_ACTIVE') and not app.config.get(
|
|
134
|
+
'TASK_COMPLETION_SIGNALED'
|
|
135
|
+
):
|
|
136
|
+
if check_task_completion(cart, app.config.get('GROUND_TRUTH_CART')):
|
|
137
|
+
app.config['TASK_COMPLETION_SIGNALED'] = True
|
|
138
|
+
app.logger.info(
|
|
139
|
+
"TASK COMPLETED: Ground truth cart state achieved."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# --- End Task Completion Helper ---
|
|
144
|
+
def create_app():
|
|
145
|
+
global app
|
|
146
|
+
try:
|
|
147
|
+
from flask import Flask, jsonify, render_template, request
|
|
148
|
+
except ImportError:
|
|
149
|
+
raise ImportError(
|
|
150
|
+
"Flask not installed. Please install it with `pip install Flask`"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Adjust template and static folder paths
|
|
154
|
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
155
|
+
app = Flask(
|
|
156
|
+
__name__,
|
|
157
|
+
template_folder=os.path.join(script_dir, 'templates'),
|
|
158
|
+
static_folder=os.path.join(script_dir, 'static'),
|
|
159
|
+
)
|
|
160
|
+
setup_logging(app)
|
|
161
|
+
|
|
162
|
+
# --- Global Task Config ---
|
|
163
|
+
app.config['TASK_ACTIVE'] = False
|
|
164
|
+
app.config['GROUND_TRUTH_CART'] = []
|
|
165
|
+
app.config['TASK_COMPLETION_SIGNALED'] = False
|
|
166
|
+
|
|
167
|
+
@app.route('/')
|
|
168
|
+
def home():
|
|
169
|
+
global ACTION_COUNT
|
|
170
|
+
ACTION_COUNT += 1
|
|
171
|
+
unique_categories = sorted({p["category"] for p in products})
|
|
172
|
+
products_by_category = {}
|
|
173
|
+
for product in products:
|
|
174
|
+
category = product["category"]
|
|
175
|
+
if category not in products_by_category:
|
|
176
|
+
products_by_category[category] = []
|
|
177
|
+
products_by_category[category].append(product)
|
|
178
|
+
|
|
179
|
+
app.logger.info(
|
|
180
|
+
"Home page requested. "
|
|
181
|
+
f"Categories: {unique_categories}. Cart count: {len(cart)}"
|
|
182
|
+
)
|
|
183
|
+
# Pass products_by_category to the template instead of the flat
|
|
184
|
+
# products list for main display
|
|
185
|
+
return render_template(
|
|
186
|
+
'index.html',
|
|
187
|
+
products_by_category=products_by_category,
|
|
188
|
+
categories=unique_categories,
|
|
189
|
+
cart_count=len(cart),
|
|
190
|
+
all_products=products,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
@app.route('/product/<int:product_id>')
|
|
194
|
+
def product_detail(product_id):
|
|
195
|
+
global ACTION_COUNT
|
|
196
|
+
ACTION_COUNT += 1
|
|
197
|
+
product = products_by_id.get(product_id)
|
|
198
|
+
app.logger.info(
|
|
199
|
+
f"Product detail page requested for ID: {product_id}. "
|
|
200
|
+
f"Cart count: {len(cart)}"
|
|
201
|
+
)
|
|
202
|
+
if product:
|
|
203
|
+
return render_template(
|
|
204
|
+
'product-detail.html', product=product, cart_count=len(cart)
|
|
205
|
+
)
|
|
206
|
+
return "Product not found", 404
|
|
207
|
+
|
|
208
|
+
@app.route('/cart')
|
|
209
|
+
def view_cart():
|
|
210
|
+
global ACTION_COUNT
|
|
211
|
+
ACTION_COUNT += 1
|
|
212
|
+
total_price = sum(
|
|
213
|
+
item['price'] * item.get('quantity', 1) for item in cart
|
|
214
|
+
)
|
|
215
|
+
app.logger.info(f"Cart page requested. Cart count: {len(cart)}")
|
|
216
|
+
return render_template(
|
|
217
|
+
'cart.html', cart_items=cart, total_price=total_price
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
@app.route('/api/products')
|
|
221
|
+
def get_products():
|
|
222
|
+
return jsonify(products)
|
|
223
|
+
|
|
224
|
+
@app.route('/api/cart', methods=['GET'])
|
|
225
|
+
def get_cart_api():
|
|
226
|
+
total_price = sum(
|
|
227
|
+
item['price'] * item.get('quantity', 1) for item in cart
|
|
228
|
+
)
|
|
229
|
+
return jsonify({'cart': cart, 'total_price': total_price})
|
|
230
|
+
|
|
231
|
+
@app.route('/api/cart/add', methods=['POST'])
|
|
232
|
+
def add_to_cart_api():
|
|
233
|
+
global ACTION_COUNT
|
|
234
|
+
data = request.json
|
|
235
|
+
product_id = data.get('productId')
|
|
236
|
+
|
|
237
|
+
if not product_id:
|
|
238
|
+
return jsonify(
|
|
239
|
+
{'success': False, 'message': 'Product ID is required'}
|
|
240
|
+
), 400
|
|
241
|
+
|
|
242
|
+
product = products_by_id.get(product_id)
|
|
243
|
+
if not product:
|
|
244
|
+
return jsonify(
|
|
245
|
+
{'success': False, 'message': 'Product not found'}
|
|
246
|
+
), 404
|
|
247
|
+
|
|
248
|
+
# Check if product is already in cart
|
|
249
|
+
for item in cart:
|
|
250
|
+
if item['id'] == product_id:
|
|
251
|
+
# If yes, just increment quantity
|
|
252
|
+
item.get('quantity', 1)
|
|
253
|
+
item['quantity'] = item.get('quantity', 1) + 1
|
|
254
|
+
app.logger.info(
|
|
255
|
+
f"Incremented quantity for product {product_id} in cart."
|
|
256
|
+
)
|
|
257
|
+
return jsonify(
|
|
258
|
+
{'success': True, 'cart': cart, 'cart_count': len(cart)}
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# If not, add new item to cart
|
|
262
|
+
cart_item = product.copy()
|
|
263
|
+
cart_item['quantity'] = 1 # Start with quantity 1
|
|
264
|
+
cart.append(cart_item)
|
|
265
|
+
ACTION_COUNT += 1 # Increment on successful add
|
|
266
|
+
|
|
267
|
+
_trigger_task_completion_check()
|
|
268
|
+
|
|
269
|
+
app.logger.info(f"Added product {product_id} to cart.")
|
|
270
|
+
return jsonify(
|
|
271
|
+
{'success': True, 'cart': cart, 'cart_count': len(cart)}
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
@app.route('/api/cart/update', methods=['POST'])
|
|
275
|
+
def update_cart_item_api():
|
|
276
|
+
data = request.json
|
|
277
|
+
product_id = data.get('productId')
|
|
278
|
+
quantity = data.get('quantity')
|
|
279
|
+
|
|
280
|
+
if not product_id:
|
|
281
|
+
return jsonify(
|
|
282
|
+
{'success': False, 'message': 'Product ID is required'}
|
|
283
|
+
), 400
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
# Ensure quantity is a non-negative integer
|
|
287
|
+
quantity = int(quantity)
|
|
288
|
+
if quantity < 0:
|
|
289
|
+
raise ValueError
|
|
290
|
+
except (ValueError, TypeError):
|
|
291
|
+
return jsonify(
|
|
292
|
+
{'success': False, 'message': 'Invalid quantity'}
|
|
293
|
+
), 400
|
|
294
|
+
|
|
295
|
+
found_item = False
|
|
296
|
+
for item in cart:
|
|
297
|
+
if item['id'] == product_id:
|
|
298
|
+
if quantity > 0:
|
|
299
|
+
item['quantity'] = quantity
|
|
300
|
+
app.logger.info(
|
|
301
|
+
"Updated quantity for product "
|
|
302
|
+
f"{product_id} to {quantity}."
|
|
303
|
+
)
|
|
304
|
+
else:
|
|
305
|
+
# If quantity is 0, remove the item
|
|
306
|
+
cart.remove(item)
|
|
307
|
+
app.logger.info(f"Removed product {product_id} from cart.")
|
|
308
|
+
found_item = True
|
|
309
|
+
break
|
|
310
|
+
|
|
311
|
+
if not found_item:
|
|
312
|
+
return jsonify(
|
|
313
|
+
{'success': False, 'message': 'Product not in cart'}
|
|
314
|
+
), 404
|
|
315
|
+
|
|
316
|
+
_trigger_task_completion_check()
|
|
317
|
+
|
|
318
|
+
total_price = sum(
|
|
319
|
+
item['price'] * item.get('quantity', 1) for item in cart
|
|
320
|
+
)
|
|
321
|
+
return jsonify(
|
|
322
|
+
{
|
|
323
|
+
'success': True,
|
|
324
|
+
'cart': cart,
|
|
325
|
+
'cart_count': len(cart),
|
|
326
|
+
'total_price': total_price,
|
|
327
|
+
}
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
@app.route('/api/cart/remove', methods=['POST'])
|
|
331
|
+
def remove_from_cart_api():
|
|
332
|
+
global cart, ACTION_COUNT
|
|
333
|
+
data = request.json
|
|
334
|
+
product_id = data.get('productId')
|
|
335
|
+
|
|
336
|
+
if not product_id:
|
|
337
|
+
return jsonify(
|
|
338
|
+
{'success': False, 'message': 'Product ID is required'}
|
|
339
|
+
), 400
|
|
340
|
+
|
|
341
|
+
original_cart_len = len(cart)
|
|
342
|
+
# List comprehension to create a new list excluding the item to be
|
|
343
|
+
# removed This is simpler than finding index and deleting
|
|
344
|
+
cart[:] = [item for item in cart if item['id'] != product_id]
|
|
345
|
+
|
|
346
|
+
if len(cart) < original_cart_len:
|
|
347
|
+
ACTION_COUNT += 1 # Increment on successful remove
|
|
348
|
+
app.logger.info(f"Removed product {product_id} from cart.")
|
|
349
|
+
|
|
350
|
+
_trigger_task_completion_check()
|
|
351
|
+
|
|
352
|
+
total_price = sum(
|
|
353
|
+
item['price'] * item.get('quantity', 1) for item in cart
|
|
354
|
+
)
|
|
355
|
+
return jsonify(
|
|
356
|
+
{
|
|
357
|
+
'success': True,
|
|
358
|
+
'cart': cart,
|
|
359
|
+
'cart_count': len(cart),
|
|
360
|
+
'total_price': total_price,
|
|
361
|
+
}
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
return jsonify(
|
|
365
|
+
{'success': False, 'message': 'Product not in cart'}
|
|
366
|
+
), 404
|
|
367
|
+
|
|
368
|
+
@app.route('/task/start', methods=['POST'])
|
|
369
|
+
def start_task():
|
|
370
|
+
global cart, ACTION_COUNT
|
|
371
|
+
ACTION_COUNT = 0 # Reset action counter
|
|
372
|
+
data = request.json
|
|
373
|
+
ground_truth = data.get('ground_truth_cart')
|
|
374
|
+
|
|
375
|
+
if not isinstance(ground_truth, list):
|
|
376
|
+
return jsonify(
|
|
377
|
+
{
|
|
378
|
+
'success': False,
|
|
379
|
+
'message': '`ground_truth_cart` must be a list.',
|
|
380
|
+
}
|
|
381
|
+
), 400
|
|
382
|
+
|
|
383
|
+
# Validate ground truth spec
|
|
384
|
+
for item_spec in ground_truth:
|
|
385
|
+
if not all(k in item_spec for k in ['id', 'quantity']):
|
|
386
|
+
return jsonify(
|
|
387
|
+
{
|
|
388
|
+
'success': False,
|
|
389
|
+
'message': (
|
|
390
|
+
'Each item in `ground_truth_cart` must have '
|
|
391
|
+
'`id` and `quantity`.'
|
|
392
|
+
),
|
|
393
|
+
}
|
|
394
|
+
), 400
|
|
395
|
+
|
|
396
|
+
app.config['TASK_ACTIVE'] = True
|
|
397
|
+
app.config['GROUND_TRUTH_CART'] = ground_truth
|
|
398
|
+
app.config['TASK_COMPLETION_SIGNALED'] = False # Reset signal
|
|
399
|
+
cart = [] # Reset cart
|
|
400
|
+
app.logger.info(f"TASK MODE STARTED. Ground truth: {ground_truth}")
|
|
401
|
+
return jsonify(
|
|
402
|
+
{'success': True, 'message': 'Task mode started. Cart reset.'}
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
@app.route('/task/check', methods=['GET'])
|
|
406
|
+
def check_task():
|
|
407
|
+
global ACTION_COUNT
|
|
408
|
+
if not app.config.get('TASK_ACTIVE'):
|
|
409
|
+
return jsonify(
|
|
410
|
+
{'success': False, 'message': 'Task mode is not active.'}
|
|
411
|
+
), 400
|
|
412
|
+
|
|
413
|
+
completed = app.config.get('TASK_COMPLETION_SIGNALED', False)
|
|
414
|
+
if completed:
|
|
415
|
+
message = "Task completed successfully."
|
|
416
|
+
else:
|
|
417
|
+
message = "Task not yet completed."
|
|
418
|
+
|
|
419
|
+
return jsonify(
|
|
420
|
+
{
|
|
421
|
+
'success': True,
|
|
422
|
+
'completed': completed,
|
|
423
|
+
'steps': ACTION_COUNT,
|
|
424
|
+
'message': message,
|
|
425
|
+
}
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
@app.route('/task/stop', methods=['POST'])
|
|
429
|
+
def stop_task():
|
|
430
|
+
global cart
|
|
431
|
+
app.config['TASK_ACTIVE'] = False
|
|
432
|
+
app.config['GROUND_TRUTH_CART'] = []
|
|
433
|
+
app.config['TASK_COMPLETION_SIGNALED'] = False
|
|
434
|
+
cart = [] # Reset cart
|
|
435
|
+
app.logger.info("TASK MODE STOPPED. Cart reset.")
|
|
436
|
+
return jsonify(
|
|
437
|
+
{'success': True, 'message': 'Task mode stopped. Cart reset.'}
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
return app
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def main():
|
|
444
|
+
parser = argparse.ArgumentParser(
|
|
445
|
+
description="Run the mock website server."
|
|
446
|
+
)
|
|
447
|
+
parser.add_argument(
|
|
448
|
+
'--port', type=int, default=5000, help='Port to run the server on.'
|
|
449
|
+
)
|
|
450
|
+
args = parser.parse_args()
|
|
451
|
+
|
|
452
|
+
# Load products specific to this app
|
|
453
|
+
load_products()
|
|
454
|
+
flask_app = create_app()
|
|
455
|
+
|
|
456
|
+
if flask_app:
|
|
457
|
+
# Use 0.0.0.0 to make it accessible from the dispatcher
|
|
458
|
+
# Disable the reloader to prevent state loss on logging
|
|
459
|
+
flask_app.run(
|
|
460
|
+
debug=True, port=args.port, host='0.0.0.0', use_reloader=False
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
if __name__ == "__main__":
|
|
465
|
+
main()
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
{
|
|
2
|
+
"products": [
|
|
3
|
+
{
|
|
4
|
+
"id": 1,
|
|
5
|
+
"name": "Gaming Laptop",
|
|
6
|
+
"price": 1200,
|
|
7
|
+
"image": "assets/img/products/laptop.jpg",
|
|
8
|
+
"category": "Electronics",
|
|
9
|
+
"rating": 4.5,
|
|
10
|
+
"description": "High-performance gaming laptop with latest specs."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"id": 2,
|
|
14
|
+
"name": "Wireless Mouse",
|
|
15
|
+
"price": 25,
|
|
16
|
+
"image": "assets/img/products/mouse.jpg",
|
|
17
|
+
"category": "Electronics",
|
|
18
|
+
"rating": 4.0,
|
|
19
|
+
"description": "Ergonomic wireless mouse with long battery life."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": 3,
|
|
23
|
+
"name": "Mechanical Keyboard",
|
|
24
|
+
"price": 75,
|
|
25
|
+
"image": "assets/img/products/keyboard.jpg",
|
|
26
|
+
"category": "Electronics",
|
|
27
|
+
"rating": 4.8,
|
|
28
|
+
"description": "RGB mechanical keyboard with customizable keys."
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": 4,
|
|
32
|
+
"name": "Coffee Maker",
|
|
33
|
+
"price": 50,
|
|
34
|
+
"image": "assets/img/products/coffeemaker.jpg",
|
|
35
|
+
"category": "Home Goods",
|
|
36
|
+
"rating": 3.9,
|
|
37
|
+
"description": "Drip coffee maker with programmable timer."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": 5,
|
|
41
|
+
"name": "Bluetooth Speaker",
|
|
42
|
+
"price": 45,
|
|
43
|
+
"image": "assets/img/products/speaker.jpg",
|
|
44
|
+
"category": "Electronics",
|
|
45
|
+
"rating": 4.2,
|
|
46
|
+
"description": "Portable Bluetooth speaker with rich sound."
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": 6,
|
|
50
|
+
"name": "Smartphone",
|
|
51
|
+
"price": 699,
|
|
52
|
+
"image": "assets/img/products/smartphone.jpg",
|
|
53
|
+
"category": "Electronics",
|
|
54
|
+
"rating": 4.6,
|
|
55
|
+
"description": "Latest-gen smartphone with stunning display and fast performance."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"id": 7,
|
|
59
|
+
"name": "Air Purifier",
|
|
60
|
+
"price": 130,
|
|
61
|
+
"image": "assets/img/products/airpurifier.jpg",
|
|
62
|
+
"category": "Home Goods",
|
|
63
|
+
"rating": 4.3,
|
|
64
|
+
"description": "HEPA air purifier with quiet operation and multiple fan speeds."
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": 8,
|
|
68
|
+
"name": "Fitness Tracker",
|
|
69
|
+
"price": 85,
|
|
70
|
+
"image": "assets/img/products/fitnesstracker.jpg",
|
|
71
|
+
"category": "Electronics",
|
|
72
|
+
"rating": 4.1,
|
|
73
|
+
"description": "Waterproof fitness tracker with heart rate and sleep monitoring."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": 9,
|
|
77
|
+
"name": "Electric Kettle",
|
|
78
|
+
"price": 35,
|
|
79
|
+
"image": "assets/img/products/kettle.jpg",
|
|
80
|
+
"category": "Home Goods",
|
|
81
|
+
"rating": 4.0,
|
|
82
|
+
"description": "Stainless steel electric kettle with auto shut-off feature."
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": 10,
|
|
86
|
+
"name": "Noise Cancelling Headphones",
|
|
87
|
+
"price": 150,
|
|
88
|
+
"image": "assets/img/products/headphones.jpg",
|
|
89
|
+
"category": "Electronics",
|
|
90
|
+
"rating": 4.7,
|
|
91
|
+
"description": "Over-ear headphones with active noise cancellation and long battery life."
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"ground_truth_cart": [
|
|
95
|
+
{
|
|
96
|
+
"id": 1,
|
|
97
|
+
"quantity": 1
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": 2,
|
|
101
|
+
"quantity": 1
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
}
|
camel/configs/__init__.py
CHANGED
|
@@ -16,6 +16,7 @@ from .anthropic_config import ANTHROPIC_API_PARAMS, AnthropicConfig
|
|
|
16
16
|
from .base_config import BaseConfig
|
|
17
17
|
from .bedrock_config import BEDROCK_API_PARAMS, BedrockConfig
|
|
18
18
|
from .cohere_config import COHERE_API_PARAMS, CohereConfig
|
|
19
|
+
from .crynux_config import CRYNUX_API_PARAMS, CrynuxConfig
|
|
19
20
|
from .deepseek_config import DEEPSEEK_API_PARAMS, DeepSeekConfig
|
|
20
21
|
from .gemini_config import Gemini_API_PARAMS, GeminiConfig
|
|
21
22
|
from .groq_config import GROQ_API_PARAMS, GroqConfig
|
|
@@ -112,4 +113,6 @@ __all__ = [
|
|
|
112
113
|
'LMStudioConfig',
|
|
113
114
|
'WatsonXConfig',
|
|
114
115
|
'WATSONX_API_PARAMS',
|
|
116
|
+
'CrynuxConfig',
|
|
117
|
+
'CRYNUX_API_PARAMS',
|
|
115
118
|
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Dict, Optional, Sequence, Type, Union
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel
|
|
19
|
+
|
|
20
|
+
from camel.configs.base_config import BaseConfig
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CrynuxConfig(BaseConfig):
|
|
24
|
+
r"""Defines the parameters for generating chat completions using the
|
|
25
|
+
OpenAI API.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
temperature (float, optional): Sampling temperature to use, between
|
|
29
|
+
:obj:`0` and :obj:`2`. Higher values make the output more random,
|
|
30
|
+
while lower values make it more focused and deterministic.
|
|
31
|
+
(default: :obj:`None`)
|
|
32
|
+
top_p (float, optional): An alternative to sampling with temperature,
|
|
33
|
+
called nucleus sampling, where the model considers the results of
|
|
34
|
+
the tokens with top_p probability mass. So :obj:`0.1` means only
|
|
35
|
+
the tokens comprising the top 10% probability mass are considered.
|
|
36
|
+
(default: :obj:`None`)
|
|
37
|
+
n (int, optional): How many chat completion choices to generate for
|
|
38
|
+
each input message. (default: :obj:`None`)
|
|
39
|
+
response_format (object, optional): An object specifying the format
|
|
40
|
+
that the model must output. Compatible with GPT-4 Turbo and all
|
|
41
|
+
GPT-3.5 Turbo models newer than gpt-3.5-turbo-1106. Setting to
|
|
42
|
+
{"type": "json_object"} enables JSON mode, which guarantees the
|
|
43
|
+
message the model generates is valid JSON. Important: when using
|
|
44
|
+
JSON mode, you must also instruct the model to produce JSON
|
|
45
|
+
yourself via a system or user message. Without this, the model
|
|
46
|
+
may generate an unending stream of whitespace until the generation
|
|
47
|
+
reaches the token limit, resulting in a long-running and seemingly
|
|
48
|
+
"stuck" request. Also note that the message content may be
|
|
49
|
+
partially cut off if finish_reason="length", which indicates the
|
|
50
|
+
generation exceeded max_tokens or the conversation exceeded the
|
|
51
|
+
max context length.
|
|
52
|
+
stream (bool, optional): If True, partial message deltas will be sent
|
|
53
|
+
as data-only server-sent events as they become available.
|
|
54
|
+
(default: :obj:`None`)
|
|
55
|
+
stop (str or list, optional): Up to :obj:`4` sequences where the API
|
|
56
|
+
will stop generating further tokens. (default: :obj:`None`)
|
|
57
|
+
max_tokens (int, optional): The maximum number of tokens to generate
|
|
58
|
+
in the chat completion. The total length of input tokens and
|
|
59
|
+
generated tokens is limited by the model's context length.
|
|
60
|
+
(default: :obj:`None`)
|
|
61
|
+
presence_penalty (float, optional): Number between :obj:`-2.0` and
|
|
62
|
+
:obj:`2.0`. Positive values penalize new tokens based on whether
|
|
63
|
+
they appear in the text so far, increasing the model's likelihood
|
|
64
|
+
to talk about new topics. See more information about frequency and
|
|
65
|
+
presence penalties. (default: :obj:`None`)
|
|
66
|
+
frequency_penalty (float, optional): Number between :obj:`-2.0` and
|
|
67
|
+
:obj:`2.0`. Positive values penalize new tokens based on their
|
|
68
|
+
existing frequency in the text so far, decreasing the model's
|
|
69
|
+
likelihood to repeat the same line verbatim. See more information
|
|
70
|
+
about frequency and presence penalties. (default: :obj:`None`)
|
|
71
|
+
logit_bias (dict, optional): Modify the likelihood of specified tokens
|
|
72
|
+
appearing in the completion. Accepts a json object that maps tokens
|
|
73
|
+
(specified by their token ID in the tokenizer) to an associated
|
|
74
|
+
bias value from :obj:`-100` to :obj:`100`. Mathematically, the bias
|
|
75
|
+
is added to the logits generated by the model prior to sampling.
|
|
76
|
+
The exact effect will vary per model, but values between:obj:` -1`
|
|
77
|
+
and :obj:`1` should decrease or increase likelihood of selection;
|
|
78
|
+
values like :obj:`-100` or :obj:`100` should result in a ban or
|
|
79
|
+
exclusive selection of the relevant token. (default: :obj:`None`)
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
temperature: Optional[float] = None
|
|
83
|
+
top_p: Optional[float] = None
|
|
84
|
+
n: Optional[int] = None
|
|
85
|
+
stream: Optional[bool] = None
|
|
86
|
+
stop: Optional[Union[str, Sequence[str]]] = None
|
|
87
|
+
max_tokens: Optional[int] = None
|
|
88
|
+
presence_penalty: Optional[float] = None
|
|
89
|
+
response_format: Optional[Union[Type[BaseModel], Dict]] = None
|
|
90
|
+
frequency_penalty: Optional[float] = None
|
|
91
|
+
logit_bias: Optional[Dict] = None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
CRYNUX_API_PARAMS = {param for param in CrynuxConfig.model_fields.keys()}
|
camel/datasets/models.py
CHANGED