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.

Files changed (59) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +95 -24
  3. camel/agents/mcp_agent.py +5 -1
  4. camel/benchmarks/mock_website/README.md +96 -0
  5. camel/benchmarks/mock_website/mock_web.py +299 -0
  6. camel/benchmarks/mock_website/requirements.txt +3 -0
  7. camel/benchmarks/mock_website/shopping_mall/app.py +465 -0
  8. camel/benchmarks/mock_website/task.json +104 -0
  9. camel/configs/__init__.py +3 -0
  10. camel/configs/crynux_config.py +94 -0
  11. camel/datasets/models.py +1 -1
  12. camel/datasets/static_dataset.py +6 -0
  13. camel/interpreters/base.py +14 -1
  14. camel/interpreters/docker/Dockerfile +63 -7
  15. camel/interpreters/docker_interpreter.py +65 -7
  16. camel/interpreters/e2b_interpreter.py +23 -8
  17. camel/interpreters/internal_python_interpreter.py +30 -2
  18. camel/interpreters/ipython_interpreter.py +21 -3
  19. camel/interpreters/subprocess_interpreter.py +34 -2
  20. camel/memories/records.py +5 -3
  21. camel/models/__init__.py +2 -0
  22. camel/models/azure_openai_model.py +101 -25
  23. camel/models/cohere_model.py +65 -0
  24. camel/models/crynux_model.py +94 -0
  25. camel/models/deepseek_model.py +43 -1
  26. camel/models/gemini_model.py +50 -4
  27. camel/models/litellm_model.py +38 -0
  28. camel/models/mistral_model.py +66 -0
  29. camel/models/model_factory.py +10 -1
  30. camel/models/openai_compatible_model.py +81 -17
  31. camel/models/openai_model.py +87 -16
  32. camel/models/reka_model.py +69 -0
  33. camel/models/samba_model.py +69 -2
  34. camel/models/sglang_model.py +74 -2
  35. camel/models/watsonx_model.py +62 -0
  36. camel/societies/workforce/role_playing_worker.py +11 -3
  37. camel/societies/workforce/single_agent_worker.py +31 -1
  38. camel/societies/workforce/utils.py +51 -0
  39. camel/societies/workforce/workforce.py +409 -7
  40. camel/storages/__init__.py +2 -0
  41. camel/storages/vectordb_storages/__init__.py +2 -0
  42. camel/storages/vectordb_storages/weaviate.py +714 -0
  43. camel/tasks/task.py +27 -10
  44. camel/toolkits/async_browser_toolkit.py +97 -54
  45. camel/toolkits/browser_toolkit.py +65 -18
  46. camel/toolkits/code_execution.py +37 -8
  47. camel/toolkits/function_tool.py +2 -2
  48. camel/toolkits/mcp_toolkit.py +13 -2
  49. camel/toolkits/playwright_mcp_toolkit.py +16 -3
  50. camel/toolkits/task_planning_toolkit.py +134 -0
  51. camel/types/enums.py +61 -2
  52. camel/types/unified_model_type.py +5 -0
  53. camel/utils/__init__.py +16 -0
  54. camel/utils/langfuse.py +258 -0
  55. camel/utils/mcp_client.py +84 -17
  56. {camel_ai-0.2.62.dist-info → camel_ai-0.2.65.dist-info}/METADATA +9 -12
  57. {camel_ai-0.2.62.dist-info → camel_ai-0.2.65.dist-info}/RECORD +59 -49
  58. {camel_ai-0.2.62.dist-info → camel_ai-0.2.65.dist-info}/WHEEL +0 -0
  59. {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__(self, timeout: Optional[float] = None) -> None:
36
- r"""Initializes the PlaywrightMCPToolkit with the specified timeout.
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-4o-mini")
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-06"
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
  ]
@@ -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