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
|
@@ -27,17 +27,29 @@ class PlaywrightMCPToolkit(BaseToolkit):
|
|
|
27
27
|
Attributes:
|
|
28
28
|
timeout (Optional[float]): Connection timeout in seconds.
|
|
29
29
|
(default: :obj:`None`)
|
|
30
|
+
additional_args (Optional[List[str]]): Additional command-line
|
|
31
|
+
arguments to pass to the Playwright MCP server. For example,
|
|
32
|
+
`["--cdp-endpoint=http://localhost:9222"]`.
|
|
33
|
+
(default: :obj:`None`)
|
|
30
34
|
|
|
31
35
|
Note:
|
|
32
36
|
Currently only supports asynchronous operation mode.
|
|
33
37
|
"""
|
|
34
38
|
|
|
35
|
-
def __init__(
|
|
36
|
-
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
timeout: Optional[float] = None,
|
|
42
|
+
additional_args: Optional[List[str]] = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
r"""Initializes the PlaywrightMCPToolkit.
|
|
37
45
|
|
|
38
46
|
Args:
|
|
39
47
|
timeout (Optional[float]): Connection timeout in seconds.
|
|
40
48
|
(default: :obj:`None`)
|
|
49
|
+
additional_args (Optional[List[str]]): Additional command-line
|
|
50
|
+
arguments to pass to the Playwright MCP server. For example,
|
|
51
|
+
`["--cdp-endpoint=http://localhost:9222"]`.
|
|
52
|
+
(default: :obj:`None`)
|
|
41
53
|
"""
|
|
42
54
|
super().__init__(timeout=timeout)
|
|
43
55
|
|
|
@@ -46,7 +58,8 @@ class PlaywrightMCPToolkit(BaseToolkit):
|
|
|
46
58
|
"mcpServers": {
|
|
47
59
|
"playwright": {
|
|
48
60
|
"command": "npx",
|
|
49
|
-
"args": ["@playwright/mcp@latest"]
|
|
61
|
+
"args": ["@playwright/mcp@latest"]
|
|
62
|
+
+ (additional_args or []),
|
|
50
63
|
}
|
|
51
64
|
}
|
|
52
65
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
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 uuid
|
|
15
|
+
from typing import List, Optional
|
|
16
|
+
|
|
17
|
+
from camel.logger import get_logger
|
|
18
|
+
from camel.tasks import Task
|
|
19
|
+
from camel.toolkits import BaseToolkit, FunctionTool
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TaskPlanningToolkit(BaseToolkit):
|
|
25
|
+
r"""A toolkit for task decomposition and re-planning."""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
timeout: Optional[float] = None,
|
|
30
|
+
):
|
|
31
|
+
r"""Initialize the TaskPlanningToolkit.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
timeout (Optional[float]): The timeout for the toolkit.
|
|
35
|
+
(default: :obj: `None`)
|
|
36
|
+
"""
|
|
37
|
+
super().__init__(timeout=timeout)
|
|
38
|
+
|
|
39
|
+
def decompose_task(
|
|
40
|
+
self,
|
|
41
|
+
original_task_content: str,
|
|
42
|
+
sub_task_contents: List[str],
|
|
43
|
+
original_task_id: Optional[str] = None,
|
|
44
|
+
) -> List[Task]:
|
|
45
|
+
r"""Use the tool to decompose an original task into several sub-tasks.
|
|
46
|
+
It creates new Task objects from the provided original task content,
|
|
47
|
+
used when the original task is complex and needs to be decomposed.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
original_task_content (str): The content of the task to be
|
|
51
|
+
decomposed.
|
|
52
|
+
sub_task_contents (List[str]): A list of strings, where each
|
|
53
|
+
string is the content for a new sub-task.
|
|
54
|
+
original_task_id (Optional[str]): The id of the task to be
|
|
55
|
+
decomposed. If not provided, a new id will be generated.
|
|
56
|
+
(default: :obj: `None`)
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
List[Task]: A list of newly created sub-task objects.
|
|
60
|
+
"""
|
|
61
|
+
# Create the original task object from its content
|
|
62
|
+
original_task = Task(
|
|
63
|
+
content=original_task_content,
|
|
64
|
+
id=original_task_id if original_task_id else str(uuid.uuid4()),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
new_tasks: List[Task] = []
|
|
68
|
+
for i, content in enumerate(sub_task_contents):
|
|
69
|
+
new_task = Task(
|
|
70
|
+
content=content,
|
|
71
|
+
id=f"{original_task.id}.{i}",
|
|
72
|
+
parent=original_task,
|
|
73
|
+
)
|
|
74
|
+
new_tasks.append(new_task)
|
|
75
|
+
original_task.subtasks.append(new_task)
|
|
76
|
+
|
|
77
|
+
logger.debug(
|
|
78
|
+
f"Decomposed task (content: '{original_task.content[:50]}...', "
|
|
79
|
+
f"id: {original_task.id}) into {len(new_tasks)} sub-tasks: "
|
|
80
|
+
f"{[task.id for task in new_tasks]}"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return new_tasks
|
|
84
|
+
|
|
85
|
+
def replan_tasks(
|
|
86
|
+
self,
|
|
87
|
+
original_task_content: str,
|
|
88
|
+
sub_task_contents: List[str],
|
|
89
|
+
original_task_id: Optional[str] = None,
|
|
90
|
+
) -> List[Task]:
|
|
91
|
+
r"""Use the tool to re_decompose a task into several subTasks.
|
|
92
|
+
It creates new Task objects from the provided original task content,
|
|
93
|
+
used when the decomposed tasks are not good enough to help finish
|
|
94
|
+
the task.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
original_task_content (str): The content of the task to be
|
|
98
|
+
decomposed.
|
|
99
|
+
sub_task_contents (List[str]): A list of strings, where each
|
|
100
|
+
string is the content for a new sub-task.
|
|
101
|
+
original_task_id (Optional[str]): The id of the task to be
|
|
102
|
+
decomposed. (default: :obj: `None`)
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
List[Task]: Reordered or modified tasks.
|
|
106
|
+
"""
|
|
107
|
+
original_task = Task(
|
|
108
|
+
content=original_task_content,
|
|
109
|
+
id=original_task_id if original_task_id else str(uuid.uuid4()),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
new_tasks: List[Task] = []
|
|
113
|
+
for i, content in enumerate(sub_task_contents):
|
|
114
|
+
new_task = Task(
|
|
115
|
+
content=content,
|
|
116
|
+
id=f"{original_task.id}.{i}",
|
|
117
|
+
parent=original_task,
|
|
118
|
+
)
|
|
119
|
+
new_tasks.append(new_task)
|
|
120
|
+
original_task.subtasks.append(new_task)
|
|
121
|
+
|
|
122
|
+
logger.debug(
|
|
123
|
+
f"RePlan task (content: '{original_task.content[:50]}...', "
|
|
124
|
+
f"id: {original_task.id}) into {len(new_tasks)} sub-tasks: "
|
|
125
|
+
f"{[task.id for task in new_tasks]}"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return new_tasks
|
|
129
|
+
|
|
130
|
+
def get_tools(self) -> List[FunctionTool]:
|
|
131
|
+
return [
|
|
132
|
+
FunctionTool(self.decompose_task),
|
|
133
|
+
FunctionTool(self.replan_tasks),
|
|
134
|
+
]
|
camel/types/enums.py
CHANGED
|
@@ -30,7 +30,7 @@ class RoleType(Enum):
|
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class ModelType(UnifiedModelType, Enum):
|
|
33
|
-
DEFAULT = os.getenv("DEFAULT_MODEL_TYPE", "gpt-
|
|
33
|
+
DEFAULT = os.getenv("DEFAULT_MODEL_TYPE", "gpt-4.1-mini-2025-04-14")
|
|
34
34
|
|
|
35
35
|
GPT_3_5_TURBO = "gpt-3.5-turbo"
|
|
36
36
|
GPT_4 = "gpt-4"
|
|
@@ -47,6 +47,7 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
47
47
|
GPT_4_1_NANO = "gpt-4.1-nano-2025-04-14"
|
|
48
48
|
O4_MINI = "o4-mini"
|
|
49
49
|
O3 = "o3"
|
|
50
|
+
O3_PRO = "o3-pro"
|
|
50
51
|
|
|
51
52
|
AWS_CLAUDE_3_7_SONNET = "anthropic.claude-3-7-sonnet-20250219-v1:0"
|
|
52
53
|
AWS_CLAUDE_3_5_SONNET = "anthropic.claude-3-5-sonnet-20241022-v2:0"
|
|
@@ -185,7 +186,7 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
185
186
|
|
|
186
187
|
# Gemini models
|
|
187
188
|
GEMINI_2_5_FLASH_PREVIEW = "gemini-2.5-flash-preview-04-17"
|
|
188
|
-
GEMINI_2_5_PRO_PREVIEW = "gemini-2.5-pro-preview-05
|
|
189
|
+
GEMINI_2_5_PRO_PREVIEW = "gemini-2.5-pro-preview-06-05"
|
|
189
190
|
GEMINI_2_0_FLASH = "gemini-2.0-flash"
|
|
190
191
|
GEMINI_2_0_FLASH_EXP = "gemini-2.0-flash-exp"
|
|
191
192
|
GEMINI_2_0_FLASH_THINKING = "gemini-2.0-flash-thinking-exp"
|
|
@@ -207,6 +208,7 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
207
208
|
MISTRAL_NEMO = "open-mistral-nemo"
|
|
208
209
|
MISTRAL_PIXTRAL_12B = "pixtral-12b-2409"
|
|
209
210
|
MISTRAL_MEDIUM_3 = "mistral-medium-latest"
|
|
211
|
+
MAGISTRAL_MEDIUM = "magistral-medium-2506"
|
|
210
212
|
|
|
211
213
|
# Reka models
|
|
212
214
|
REKA_CORE = "reka-core"
|
|
@@ -384,6 +386,25 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
384
386
|
)
|
|
385
387
|
WATSONX_MISTRAL_LARGE = "mistralai/mistral-large"
|
|
386
388
|
|
|
389
|
+
# Crynux models
|
|
390
|
+
CRYNUX_DEEPSEEK_R1_DISTILL_QWEN_1_5B = (
|
|
391
|
+
"deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B"
|
|
392
|
+
)
|
|
393
|
+
CRYNUX_DEEPSEEK_R1_DISTILL_QWEN_7B = (
|
|
394
|
+
"deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"
|
|
395
|
+
)
|
|
396
|
+
CRYNUX_DEEPSEEK_R1_DISTILL_LLAMA_8B = (
|
|
397
|
+
"deepseek-ai/DeepSeek-R1-Distill-Llama-8B"
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
CRYNUX_QWEN_3_4_B = "Qwen/Qwen3-4B"
|
|
401
|
+
CRYNUX_QWEN_3_8_B = "Qwen/Qwen3-8B"
|
|
402
|
+
CRYNUX_QWEN_2_5_7B = "Qwen/Qwen2.5-7B"
|
|
403
|
+
CRYNUX_QWEN_2_5_7B_INSTRUCT = "Qwen/Qwen2.5-7B-Instruct"
|
|
404
|
+
|
|
405
|
+
CRYNUX_NOUS_HERMES_3_LLAMA_3_1_8B = "NousResearch/Hermes-3-Llama-3.1-8B"
|
|
406
|
+
CRYNUX_NOUS_HERMES_3_LLAMA_3_2_3B = "NousResearch/Hermes-3-Llama-3.2-3B"
|
|
407
|
+
|
|
387
408
|
def __str__(self):
|
|
388
409
|
return self.value
|
|
389
410
|
|
|
@@ -452,6 +473,7 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
452
473
|
ModelType.O1,
|
|
453
474
|
ModelType.O1_PREVIEW,
|
|
454
475
|
ModelType.O1_MINI,
|
|
476
|
+
ModelType.O3_PRO,
|
|
455
477
|
ModelType.O3_MINI,
|
|
456
478
|
ModelType.GPT_4_5_PREVIEW,
|
|
457
479
|
ModelType.GPT_4_1,
|
|
@@ -492,6 +514,7 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
492
514
|
ModelType.O1_PREVIEW,
|
|
493
515
|
ModelType.O1_MINI,
|
|
494
516
|
ModelType.O3_MINI,
|
|
517
|
+
ModelType.O3_PRO,
|
|
495
518
|
ModelType.GPT_4_5_PREVIEW,
|
|
496
519
|
ModelType.GPT_4_1,
|
|
497
520
|
ModelType.GPT_4_1_MINI,
|
|
@@ -612,6 +635,7 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
612
635
|
ModelType.MISTRAL_8B,
|
|
613
636
|
ModelType.MISTRAL_3B,
|
|
614
637
|
ModelType.MISTRAL_MEDIUM_3,
|
|
638
|
+
ModelType.MAGISTRAL_MEDIUM,
|
|
615
639
|
}
|
|
616
640
|
|
|
617
641
|
@property
|
|
@@ -891,6 +915,20 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
891
915
|
ModelType.NOVITA_L31_70B_EURYALE_V2_2,
|
|
892
916
|
}
|
|
893
917
|
|
|
918
|
+
@property
|
|
919
|
+
def is_crynux(self) -> bool:
|
|
920
|
+
return self in {
|
|
921
|
+
ModelType.CRYNUX_DEEPSEEK_R1_DISTILL_QWEN_1_5B,
|
|
922
|
+
ModelType.CRYNUX_DEEPSEEK_R1_DISTILL_QWEN_7B,
|
|
923
|
+
ModelType.CRYNUX_DEEPSEEK_R1_DISTILL_LLAMA_8B,
|
|
924
|
+
ModelType.CRYNUX_QWEN_3_4_B,
|
|
925
|
+
ModelType.CRYNUX_QWEN_3_8_B,
|
|
926
|
+
ModelType.CRYNUX_QWEN_2_5_7B,
|
|
927
|
+
ModelType.CRYNUX_QWEN_2_5_7B_INSTRUCT,
|
|
928
|
+
ModelType.CRYNUX_NOUS_HERMES_3_LLAMA_3_1_8B,
|
|
929
|
+
ModelType.CRYNUX_NOUS_HERMES_3_LLAMA_3_2_3B,
|
|
930
|
+
}
|
|
931
|
+
|
|
894
932
|
@property
|
|
895
933
|
def is_aiml(self) -> bool:
|
|
896
934
|
return self in {
|
|
@@ -991,6 +1029,15 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
991
1029
|
ModelType.NOVITA_GLM_4_32B_0414,
|
|
992
1030
|
ModelType.NOVITA_GLM_Z1_RUMINATION_32B_0414,
|
|
993
1031
|
ModelType.NOVITA_QWEN_2_5_7B,
|
|
1032
|
+
ModelType.CRYNUX_DEEPSEEK_R1_DISTILL_QWEN_1_5B,
|
|
1033
|
+
ModelType.CRYNUX_DEEPSEEK_R1_DISTILL_QWEN_7B,
|
|
1034
|
+
ModelType.CRYNUX_DEEPSEEK_R1_DISTILL_LLAMA_8B,
|
|
1035
|
+
ModelType.CRYNUX_QWEN_3_4_B,
|
|
1036
|
+
ModelType.CRYNUX_QWEN_3_8_B,
|
|
1037
|
+
ModelType.CRYNUX_QWEN_2_5_7B,
|
|
1038
|
+
ModelType.CRYNUX_QWEN_2_5_7B_INSTRUCT,
|
|
1039
|
+
ModelType.CRYNUX_NOUS_HERMES_3_LLAMA_3_1_8B,
|
|
1040
|
+
ModelType.CRYNUX_NOUS_HERMES_3_LLAMA_3_2_3B,
|
|
994
1041
|
}:
|
|
995
1042
|
return 32_000
|
|
996
1043
|
elif self in {
|
|
@@ -1163,6 +1210,7 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
1163
1210
|
elif self in {
|
|
1164
1211
|
ModelType.O1,
|
|
1165
1212
|
ModelType.O3_MINI,
|
|
1213
|
+
ModelType.O3_PRO,
|
|
1166
1214
|
ModelType.CLAUDE_2_1,
|
|
1167
1215
|
ModelType.CLAUDE_3_OPUS,
|
|
1168
1216
|
ModelType.CLAUDE_3_SONNET,
|
|
@@ -1223,6 +1271,11 @@ class ModelType(UnifiedModelType, Enum):
|
|
|
1223
1271
|
ModelType.TOGETHER_LLAMA_4_SCOUT,
|
|
1224
1272
|
}:
|
|
1225
1273
|
return 10_000_000
|
|
1274
|
+
elif self in {
|
|
1275
|
+
ModelType.MAGISTRAL_MEDIUM,
|
|
1276
|
+
}:
|
|
1277
|
+
return 40_000
|
|
1278
|
+
|
|
1226
1279
|
else:
|
|
1227
1280
|
logger.warning(
|
|
1228
1281
|
f"Unknown model type {self}, set maximum token limit "
|
|
@@ -1449,6 +1502,7 @@ class ModelPlatformType(Enum):
|
|
|
1449
1502
|
NETMIND = "netmind"
|
|
1450
1503
|
NOVITA = "novita"
|
|
1451
1504
|
WATSONX = "watsonx"
|
|
1505
|
+
CRYNUX = "crynux"
|
|
1452
1506
|
|
|
1453
1507
|
@classmethod
|
|
1454
1508
|
def from_name(cls, name):
|
|
@@ -1624,6 +1678,11 @@ class ModelPlatformType(Enum):
|
|
|
1624
1678
|
r"""Returns whether this platform is WatsonX."""
|
|
1625
1679
|
return self is ModelPlatformType.WATSONX
|
|
1626
1680
|
|
|
1681
|
+
@property
|
|
1682
|
+
def is_crynux(self) -> bool:
|
|
1683
|
+
r"""Returns whether this platform is Crynux."""
|
|
1684
|
+
return self is ModelPlatformType.CRYNUX
|
|
1685
|
+
|
|
1627
1686
|
|
|
1628
1687
|
class AudioModelType(Enum):
|
|
1629
1688
|
TTS_1 = "tts-1"
|
|
@@ -163,6 +163,11 @@ class UnifiedModelType(str):
|
|
|
163
163
|
r"""Returns whether the model is a WatsonX served model."""
|
|
164
164
|
return True
|
|
165
165
|
|
|
166
|
+
@property
|
|
167
|
+
def is_crynux(self) -> bool:
|
|
168
|
+
r"""Returns whether the model is a Crynux served model."""
|
|
169
|
+
return True
|
|
170
|
+
|
|
166
171
|
@property
|
|
167
172
|
def support_native_structured_output(self) -> bool:
|
|
168
173
|
r"""Returns whether the model supports native structured output."""
|
camel/utils/__init__.py
CHANGED
|
@@ -44,6 +44,15 @@ from .commons import (
|
|
|
44
44
|
from .constants import Constants
|
|
45
45
|
from .deduplication import DeduplicationResult, deduplicate_internally
|
|
46
46
|
from .filename import sanitize_filename
|
|
47
|
+
from .langfuse import (
|
|
48
|
+
configure_langfuse,
|
|
49
|
+
get_current_agent_session_id,
|
|
50
|
+
get_langfuse_status,
|
|
51
|
+
is_langfuse_available,
|
|
52
|
+
observe,
|
|
53
|
+
update_current_observation,
|
|
54
|
+
update_langfuse_trace,
|
|
55
|
+
)
|
|
47
56
|
from .mcp import MCPServer
|
|
48
57
|
from .response_format import get_pydantic_model, model_from_json_schema
|
|
49
58
|
from .token_counting import (
|
|
@@ -97,4 +106,11 @@ __all__ = [
|
|
|
97
106
|
"sanitize_filename",
|
|
98
107
|
"browser_toolkit_save_auth_cookie",
|
|
99
108
|
"run_async",
|
|
109
|
+
"configure_langfuse",
|
|
110
|
+
"is_langfuse_available",
|
|
111
|
+
"get_current_agent_session_id",
|
|
112
|
+
"update_langfuse_trace",
|
|
113
|
+
"observe",
|
|
114
|
+
"update_current_observation",
|
|
115
|
+
"get_langfuse_status",
|
|
100
116
|
]
|
camel/utils/langfuse.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
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 os
|
|
15
|
+
import threading
|
|
16
|
+
from typing import Any, Dict, List, Optional
|
|
17
|
+
|
|
18
|
+
from camel.logger import get_logger
|
|
19
|
+
from camel.utils import dependencies_required
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
# Thread-local storage for agent session IDs
|
|
23
|
+
_local = threading.local()
|
|
24
|
+
|
|
25
|
+
# Global flag to track if Langfuse has been configured
|
|
26
|
+
_langfuse_configured = False
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
from langfuse.decorators import langfuse_context
|
|
30
|
+
|
|
31
|
+
LANGFUSE_AVAILABLE = True
|
|
32
|
+
except ImportError:
|
|
33
|
+
LANGFUSE_AVAILABLE = False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dependencies_required('langfuse')
|
|
37
|
+
def configure_langfuse(
|
|
38
|
+
public_key: Optional[str] = None,
|
|
39
|
+
secret_key: Optional[str] = None,
|
|
40
|
+
host: Optional[str] = None,
|
|
41
|
+
debug: Optional[bool] = None,
|
|
42
|
+
enabled: Optional[bool] = None,
|
|
43
|
+
):
|
|
44
|
+
r"""Configure Langfuse for CAMEL models.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
public_key(Optional[str]): Langfuse public key. Can be set via LANGFUSE_PUBLIC_KEY.
|
|
48
|
+
(default: :obj:`None`)
|
|
49
|
+
secret_key(Optional[str]): Langfuse secret key. Can be set via LANGFUSE_SECRET_KEY.
|
|
50
|
+
(default: :obj:`None`)
|
|
51
|
+
host(Optional[str]): Langfuse host URL. Can be set via LANGFUSE_HOST.
|
|
52
|
+
(default: :obj:`https://cloud.langfuse.com`)
|
|
53
|
+
debug(Optional[bool]): Enable debug mode. Can be set via LANGFUSE_DEBUG.
|
|
54
|
+
(default: :obj:`None`)
|
|
55
|
+
enabled(Optional[bool]): Enable/disable tracing. Can be set via LANGFUSE_ENABLED.
|
|
56
|
+
(default: :obj:`None`)
|
|
57
|
+
|
|
58
|
+
Note:
|
|
59
|
+
This function configures the native langfuse_context which works with
|
|
60
|
+
@observe() decorators. Set enabled=False to disable all tracing.
|
|
61
|
+
""" # noqa: E501
|
|
62
|
+
global _langfuse_configured
|
|
63
|
+
|
|
64
|
+
# Get configuration from environment or parameters
|
|
65
|
+
public_key = public_key or os.environ.get("LANGFUSE_PUBLIC_KEY")
|
|
66
|
+
secret_key = secret_key or os.environ.get("LANGFUSE_SECRET_KEY")
|
|
67
|
+
host = host or os.environ.get(
|
|
68
|
+
"LANGFUSE_HOST", "https://cloud.langfuse.com"
|
|
69
|
+
)
|
|
70
|
+
debug = (
|
|
71
|
+
debug
|
|
72
|
+
if debug is not None
|
|
73
|
+
else os.environ.get("LANGFUSE_DEBUG", "False").lower() == "true"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Handle enabled parameter
|
|
77
|
+
if enabled is None:
|
|
78
|
+
env_enabled_str = os.environ.get("LANGFUSE_ENABLED")
|
|
79
|
+
if env_enabled_str is not None:
|
|
80
|
+
enabled = env_enabled_str.lower() == "true"
|
|
81
|
+
else:
|
|
82
|
+
enabled = False # Default to disabled
|
|
83
|
+
|
|
84
|
+
# If not enabled, don't configure anything and don't call langfuse function
|
|
85
|
+
if not enabled:
|
|
86
|
+
_langfuse_configured = False
|
|
87
|
+
logger.info("Langfuse tracing disabled for CAMEL models")
|
|
88
|
+
|
|
89
|
+
logger.debug(
|
|
90
|
+
f"Configuring Langfuse - enabled: {enabled}, "
|
|
91
|
+
f"public_key: {'***' + public_key[-4:] if public_key else None}, "
|
|
92
|
+
f"host: {host}, debug: {debug}"
|
|
93
|
+
)
|
|
94
|
+
if enabled and public_key and secret_key and LANGFUSE_AVAILABLE:
|
|
95
|
+
_langfuse_configured = True
|
|
96
|
+
else:
|
|
97
|
+
_langfuse_configured = False
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
# Configure langfuse_context with native method
|
|
101
|
+
langfuse_context.configure(
|
|
102
|
+
public_key=public_key,
|
|
103
|
+
secret_key=secret_key,
|
|
104
|
+
host=host,
|
|
105
|
+
debug=debug,
|
|
106
|
+
enabled=True, # Always True here since we checked enabled above
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
logger.info("Langfuse tracing enabled for CAMEL models")
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"Failed to configure Langfuse: {e}")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def is_langfuse_available() -> bool:
|
|
116
|
+
r"""Check if Langfuse is configured."""
|
|
117
|
+
return _langfuse_configured
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def set_current_agent_session_id(session_id: str) -> None:
|
|
121
|
+
r"""Set the session ID for the current agent in thread-local storage.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
session_id(str): The session ID to set for the current agent.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
_local.agent_session_id = session_id
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_current_agent_session_id() -> Optional[str]:
|
|
131
|
+
r"""Get the session ID for the current agent from thread-local storage.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Optional[str]: The session ID for the current agent.
|
|
135
|
+
"""
|
|
136
|
+
if is_langfuse_available():
|
|
137
|
+
return getattr(_local, 'agent_session_id', None)
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def update_langfuse_trace(
|
|
142
|
+
session_id: Optional[str] = None,
|
|
143
|
+
user_id: Optional[str] = None,
|
|
144
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
145
|
+
tags: Optional[List[str]] = None,
|
|
146
|
+
) -> bool:
|
|
147
|
+
r"""Update the current Langfuse trace with session ID and metadata.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
session_id(Optional[str]): Optional session ID to use. If :obj:`None`
|
|
151
|
+
uses the current agent's session ID. (default: :obj:`None`)
|
|
152
|
+
user_id(Optional[str]): Optional user ID for the trace.
|
|
153
|
+
(default: :obj:`None`)
|
|
154
|
+
metadata(Optional[Dict[str, Any]]): Optional metadata dictionary.
|
|
155
|
+
(default: :obj:`None`)
|
|
156
|
+
tags(Optional[List[str]]): Optional list of tags.
|
|
157
|
+
(default: :obj:`None`)
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
bool: True if update was successful, False otherwise.
|
|
161
|
+
"""
|
|
162
|
+
if not is_langfuse_available():
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
# Use provided session_id or get from thread-local storage
|
|
166
|
+
final_session_id = session_id or get_current_agent_session_id()
|
|
167
|
+
|
|
168
|
+
update_data: Dict[str, Any] = {}
|
|
169
|
+
if final_session_id:
|
|
170
|
+
update_data["session_id"] = final_session_id
|
|
171
|
+
if user_id:
|
|
172
|
+
update_data["user_id"] = user_id
|
|
173
|
+
if metadata:
|
|
174
|
+
update_data["metadata"] = metadata
|
|
175
|
+
if tags:
|
|
176
|
+
update_data["tags"] = tags
|
|
177
|
+
|
|
178
|
+
if update_data:
|
|
179
|
+
langfuse_context.update_current_trace(**update_data)
|
|
180
|
+
return True
|
|
181
|
+
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def update_current_observation(
|
|
186
|
+
input: Optional[Dict[str, Any]] = None,
|
|
187
|
+
output: Optional[Dict[str, Any]] = None,
|
|
188
|
+
model: Optional[str] = None,
|
|
189
|
+
model_parameters: Optional[Dict[str, Any]] = None,
|
|
190
|
+
usage_details: Optional[Dict[str, Any]] = None,
|
|
191
|
+
**kwargs,
|
|
192
|
+
) -> None:
|
|
193
|
+
r"""Update the current Langfuse observation with input, output,
|
|
194
|
+
model, model_parameters, and usage_details.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
input(Optional[Dict[str, Any]]): Optional input dictionary.
|
|
198
|
+
(default: :obj:`None`)
|
|
199
|
+
output(Optional[Dict[str, Any]]): Optional output dictionary.
|
|
200
|
+
(default: :obj:`None`)
|
|
201
|
+
model(Optional[str]): Optional model name. (default: :obj:`None`)
|
|
202
|
+
model_parameters(Optional[Dict[str, Any]]): Optional model parameters
|
|
203
|
+
dictionary. (default: :obj:`None`)
|
|
204
|
+
usage_details(Optional[Dict[str, Any]]): Optional usage details
|
|
205
|
+
dictionary. (default: :obj:`None`)
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
None
|
|
209
|
+
"""
|
|
210
|
+
if not is_langfuse_available():
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
langfuse_context.update_current_observation(
|
|
214
|
+
input=input,
|
|
215
|
+
output=output,
|
|
216
|
+
model=model,
|
|
217
|
+
model_parameters=model_parameters,
|
|
218
|
+
usage_details=usage_details,
|
|
219
|
+
**kwargs,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def get_langfuse_status() -> Dict[str, Any]:
|
|
224
|
+
r"""Get detailed Langfuse configuration status for debugging.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Dict[str, Any]: Status information including configuration state.
|
|
228
|
+
"""
|
|
229
|
+
env_enabled_str = os.environ.get("LANGFUSE_ENABLED")
|
|
230
|
+
env_enabled = (
|
|
231
|
+
env_enabled_str.lower() == "true" if env_enabled_str else None
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
status = {
|
|
235
|
+
"configured": _langfuse_configured,
|
|
236
|
+
"has_public_key": bool(os.environ.get("LANGFUSE_PUBLIC_KEY")),
|
|
237
|
+
"has_secret_key": bool(os.environ.get("LANGFUSE_SECRET_KEY")),
|
|
238
|
+
"env_enabled": env_enabled,
|
|
239
|
+
"host": os.environ.get("LANGFUSE_HOST", "https://cloud.langfuse.com"),
|
|
240
|
+
"debug": os.environ.get("LANGFUSE_DEBUG", "false").lower() == "true",
|
|
241
|
+
"current_session_id": get_current_agent_session_id(),
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if _langfuse_configured:
|
|
245
|
+
try:
|
|
246
|
+
# Try to get some context information
|
|
247
|
+
status["langfuse_context_available"] = True
|
|
248
|
+
except Exception as e:
|
|
249
|
+
status["langfuse_context_error"] = str(e)
|
|
250
|
+
|
|
251
|
+
return status
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def observe(*args, **kwargs):
|
|
255
|
+
def decorator(func):
|
|
256
|
+
return func
|
|
257
|
+
|
|
258
|
+
return decorator
|