camel-ai 0.2.15a0__py3-none-any.whl → 0.2.17__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 (95) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +18 -4
  3. camel/agents/multi_hop_generator_agent.py +85 -0
  4. camel/agents/programmed_agent_instruction.py +148 -0
  5. camel/benchmarks/__init__.py +13 -1
  6. camel/benchmarks/apibank.py +565 -0
  7. camel/benchmarks/apibench.py +500 -0
  8. camel/benchmarks/gaia.py +4 -4
  9. camel/benchmarks/nexus.py +518 -0
  10. camel/benchmarks/ragbench.py +333 -0
  11. camel/bots/__init__.py +1 -1
  12. camel/bots/discord/__init__.py +26 -0
  13. camel/bots/discord/discord_app.py +384 -0
  14. camel/bots/discord/discord_installation.py +64 -0
  15. camel/bots/discord/discord_store.py +160 -0
  16. camel/configs/__init__.py +3 -0
  17. camel/configs/anthropic_config.py +17 -15
  18. camel/configs/internlm_config.py +60 -0
  19. camel/data_collector/base.py +5 -5
  20. camel/data_collector/sharegpt_collector.py +2 -2
  21. camel/datagen/__init__.py +6 -2
  22. camel/datagen/{o1datagen.py → cotdatagen.py} +19 -6
  23. camel/datagen/self_instruct/__init__.py +36 -0
  24. camel/datagen/self_instruct/filter/__init__.py +34 -0
  25. camel/datagen/self_instruct/filter/filter_function.py +216 -0
  26. camel/datagen/self_instruct/filter/filter_registry.py +56 -0
  27. camel/datagen/self_instruct/filter/instruction_filter.py +81 -0
  28. camel/datagen/self_instruct/self_instruct.py +393 -0
  29. camel/datagen/self_instruct/templates.py +382 -0
  30. camel/datahubs/huggingface.py +12 -2
  31. camel/datahubs/models.py +2 -3
  32. camel/embeddings/mistral_embedding.py +5 -1
  33. camel/embeddings/openai_compatible_embedding.py +6 -1
  34. camel/embeddings/openai_embedding.py +5 -1
  35. camel/interpreters/e2b_interpreter.py +5 -1
  36. camel/loaders/__init__.py +2 -0
  37. camel/loaders/apify_reader.py +5 -1
  38. camel/loaders/chunkr_reader.py +5 -1
  39. camel/loaders/firecrawl_reader.py +0 -30
  40. camel/loaders/panda_reader.py +337 -0
  41. camel/logger.py +11 -5
  42. camel/messages/__init__.py +10 -4
  43. camel/messages/conversion/conversation_models.py +5 -0
  44. camel/messages/func_message.py +30 -22
  45. camel/models/__init__.py +2 -0
  46. camel/models/anthropic_model.py +6 -23
  47. camel/models/azure_openai_model.py +1 -2
  48. camel/models/cohere_model.py +13 -1
  49. camel/models/deepseek_model.py +5 -1
  50. camel/models/gemini_model.py +15 -2
  51. camel/models/groq_model.py +5 -1
  52. camel/models/internlm_model.py +143 -0
  53. camel/models/mistral_model.py +19 -8
  54. camel/models/model_factory.py +3 -0
  55. camel/models/nemotron_model.py +5 -1
  56. camel/models/nvidia_model.py +5 -1
  57. camel/models/openai_model.py +5 -1
  58. camel/models/qwen_model.py +5 -1
  59. camel/models/reka_model.py +5 -1
  60. camel/models/reward/__init__.py +2 -0
  61. camel/models/reward/nemotron_model.py +5 -1
  62. camel/models/reward/skywork_model.py +88 -0
  63. camel/models/samba_model.py +5 -1
  64. camel/models/togetherai_model.py +5 -1
  65. camel/models/yi_model.py +5 -1
  66. camel/models/zhipuai_model.py +5 -1
  67. camel/schemas/openai_converter.py +5 -1
  68. camel/storages/graph_storages/nebula_graph.py +89 -20
  69. camel/storages/graph_storages/neo4j_graph.py +138 -0
  70. camel/synthetic_datagen/source2synth/data_processor.py +373 -0
  71. camel/synthetic_datagen/source2synth/models.py +68 -0
  72. camel/synthetic_datagen/source2synth/user_data_processor_config.py +73 -0
  73. camel/toolkits/__init__.py +4 -0
  74. camel/toolkits/arxiv_toolkit.py +20 -3
  75. camel/toolkits/dappier_toolkit.py +196 -0
  76. camel/toolkits/function_tool.py +61 -61
  77. camel/toolkits/google_scholar_toolkit.py +9 -0
  78. camel/toolkits/meshy_toolkit.py +5 -1
  79. camel/toolkits/notion_toolkit.py +1 -1
  80. camel/toolkits/openbb_toolkit.py +869 -0
  81. camel/toolkits/search_toolkit.py +91 -5
  82. camel/toolkits/stripe_toolkit.py +5 -1
  83. camel/toolkits/twitter_toolkit.py +24 -16
  84. camel/types/__init__.py +4 -2
  85. camel/types/enums.py +34 -1
  86. camel/types/openai_types.py +6 -4
  87. camel/types/unified_model_type.py +5 -0
  88. camel/utils/__init__.py +2 -0
  89. camel/utils/commons.py +104 -19
  90. camel/utils/token_counting.py +3 -3
  91. {camel_ai-0.2.15a0.dist-info → camel_ai-0.2.17.dist-info}/METADATA +160 -177
  92. {camel_ai-0.2.15a0.dist-info → camel_ai-0.2.17.dist-info}/RECORD +94 -69
  93. {camel_ai-0.2.15a0.dist-info → camel_ai-0.2.17.dist-info}/WHEEL +1 -1
  94. camel/bots/discord_app.py +0 -138
  95. {camel_ai-0.2.15a0.dist-info → camel_ai-0.2.17.dist-info}/LICENSE +0 -0
@@ -13,9 +13,10 @@
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
  import os
15
15
  import xml.etree.ElementTree as ET
16
- from typing import Any, Dict, List, Optional, TypeAlias, Union
16
+ from typing import Any, Dict, List, Literal, Optional, Type, TypeAlias, Union
17
17
 
18
18
  import requests
19
+ from pydantic import BaseModel
19
20
 
20
21
  from camel.toolkits.base import BaseToolkit
21
22
  from camel.toolkits.function_tool import FunctionTool
@@ -63,6 +64,82 @@ class SearchToolkit(BaseToolkit):
63
64
 
64
65
  return result
65
66
 
67
+ @dependencies_required("linkup")
68
+ @api_keys_required(
69
+ [
70
+ (None, "LINKUP_API_KEY"),
71
+ ]
72
+ )
73
+ def search_linkup(
74
+ self,
75
+ query: str,
76
+ depth: Literal["standard", "deep"] = "standard",
77
+ output_type: Literal[
78
+ "searchResults", "sourcedAnswer", "structured"
79
+ ] = "searchResults",
80
+ structured_output_schema: Union[Type[BaseModel], str, None] = None,
81
+ ) -> Dict[str, Any]:
82
+ r"""Search for a query in the Linkup API and return results in various
83
+ formats.
84
+
85
+ Args:
86
+ query (str): The search query.
87
+ depth (Literal["standard", "deep"]): The depth of the search.
88
+ "standard" for a straightforward search, "deep" for a more
89
+ comprehensive search.
90
+ output_type (Literal["searchResults", "sourcedAnswer",
91
+ "structured"]): The type of output:
92
+ - "searchResults" for raw search results,
93
+ - "sourcedAnswer" for an answer with supporting sources,
94
+ - "structured" for output based on a provided schema.
95
+ structured_output_schema (Union[Type[BaseModel], str, None]): If
96
+ `output_type` is "structured",specify the schema of the
97
+ output. Can be a Pydantic BaseModel or a JSON schema string.
98
+
99
+ Returns:
100
+ Dict[str, Any]: A dictionary representing the search result. The
101
+ structure depends on the `output_type`. If an error occurs,
102
+ returns an error message.
103
+ """
104
+ try:
105
+ from linkup import LinkupClient
106
+
107
+ # Initialize the Linkup client with the API key
108
+ LINKUP_API_KEY = os.getenv("LINKUP_API_KEY")
109
+ client = LinkupClient(api_key=LINKUP_API_KEY)
110
+
111
+ # Perform the search using the specified output_type
112
+ response = client.search(
113
+ query=query,
114
+ depth=depth,
115
+ output_type=output_type,
116
+ structured_output_schema=structured_output_schema,
117
+ )
118
+
119
+ if output_type == "searchResults":
120
+ results = [
121
+ item.__dict__
122
+ for item in response.__dict__.get('results', [])
123
+ ]
124
+ return {"results": results}
125
+
126
+ elif output_type == "sourcedAnswer":
127
+ answer = response.__dict__.get('answer', '')
128
+ sources = [
129
+ item.__dict__
130
+ for item in response.__dict__.get('sources', [])
131
+ ]
132
+ return {"answer": answer, "sources": sources}
133
+
134
+ elif output_type == "structured" and structured_output_schema:
135
+ return response.__dict__
136
+
137
+ else:
138
+ return {"error": f"Invalid output_type: {output_type}"}
139
+
140
+ except Exception as e:
141
+ return {"error": f"An unexpected error occurred: {e!s}"}
142
+
66
143
  @dependencies_required("duckduckgo_search")
67
144
  def search_duckduckgo(
68
145
  self, query: str, source: str = "text", max_results: int = 5
@@ -151,7 +228,11 @@ class SearchToolkit(BaseToolkit):
151
228
  # If no answer found, return an empty list
152
229
  return responses
153
230
 
154
- @api_keys_required("BRAVE_API_KEY")
231
+ @api_keys_required(
232
+ [
233
+ (None, 'BRAVE_API_KEY'),
234
+ ]
235
+ )
155
236
  def search_brave(
156
237
  self,
157
238
  q: str,
@@ -297,7 +378,12 @@ class SearchToolkit(BaseToolkit):
297
378
  data = response.json()["web"]
298
379
  return data
299
380
 
300
- @api_keys_required("GOOGLE_API_KEY", "SEARCH_ENGINE_ID")
381
+ @api_keys_required(
382
+ [
383
+ (None, 'GOOGLE_API_KEY'),
384
+ (None, 'SEARCH_ENGINE_ID'),
385
+ ]
386
+ )
301
387
  def search_google(
302
388
  self, query: str, num_result_pages: int = 5
303
389
  ) -> List[Dict[str, Any]]:
@@ -429,8 +515,7 @@ class SearchToolkit(BaseToolkit):
429
515
  if not WOLFRAMALPHA_APP_ID:
430
516
  raise ValueError(
431
517
  "`WOLFRAMALPHA_APP_ID` not found in environment "
432
- "variables. Get `WOLFRAMALPHA_APP_ID` here: "
433
- "`https://products.wolframalpha.com/api/`."
518
+ "variables. Get `WOLFRAMALPHA_APP_ID` here: `https://products.wolframalpha.com/api/`."
434
519
  )
435
520
 
436
521
  try:
@@ -618,6 +703,7 @@ class SearchToolkit(BaseToolkit):
618
703
  """
619
704
  return [
620
705
  FunctionTool(self.search_wiki),
706
+ FunctionTool(self.search_linkup),
621
707
  FunctionTool(self.search_google),
622
708
  FunctionTool(self.search_duckduckgo),
623
709
  FunctionTool(self.query_wolfram_alpha),
@@ -36,7 +36,11 @@ class StripeToolkit(BaseToolkit):
36
36
  logger (Logger): a logger to write logs.
37
37
  """
38
38
 
39
- @api_keys_required("STRIPE_API_KEY")
39
+ @api_keys_required(
40
+ [
41
+ (None, "STRIPE_API_KEY"),
42
+ ]
43
+ )
40
44
  def __init__(self, retries: int = 3):
41
45
  r"""Initializes the StripeToolkit with the specified number of
42
46
  retries.
@@ -31,10 +31,12 @@ logger = get_logger(__name__)
31
31
 
32
32
 
33
33
  @api_keys_required(
34
- "TWITTER_CONSUMER_KEY",
35
- "TWITTER_CONSUMER_SECRET",
36
- "TWITTER_ACCESS_TOKEN",
37
- "TWITTER_ACCESS_TOKEN_SECRET",
34
+ [
35
+ (None, "TWITTER_CONSUMER_KEY"),
36
+ (None, "TWITTER_CONSUMER_SECRET"),
37
+ (None, "TWITTER_ACCESS_TOKEN"),
38
+ (None, "TWITTER_ACCESS_TOKEN_SECRET"),
39
+ ]
38
40
  )
39
41
  def create_tweet(
40
42
  text: str,
@@ -132,10 +134,12 @@ def create_tweet(
132
134
 
133
135
 
134
136
  @api_keys_required(
135
- "TWITTER_CONSUMER_KEY",
136
- "TWITTER_CONSUMER_SECRET",
137
- "TWITTER_ACCESS_TOKEN",
138
- "TWITTER_ACCESS_TOKEN_SECRET",
137
+ [
138
+ (None, "TWITTER_CONSUMER_KEY"),
139
+ (None, "TWITTER_CONSUMER_SECRET"),
140
+ (None, "TWITTER_ACCESS_TOKEN"),
141
+ (None, "TWITTER_ACCESS_TOKEN_SECRET"),
142
+ ]
139
143
  )
140
144
  def delete_tweet(tweet_id: str) -> str:
141
145
  r"""Deletes a tweet with the specified ID for an authorized user.
@@ -187,10 +191,12 @@ def delete_tweet(tweet_id: str) -> str:
187
191
 
188
192
 
189
193
  @api_keys_required(
190
- "TWITTER_CONSUMER_KEY",
191
- "TWITTER_CONSUMER_SECRET",
192
- "TWITTER_ACCESS_TOKEN",
193
- "TWITTER_ACCESS_TOKEN_SECRET",
194
+ [
195
+ (None, "TWITTER_CONSUMER_KEY"),
196
+ (None, "TWITTER_CONSUMER_SECRET"),
197
+ (None, "TWITTER_ACCESS_TOKEN"),
198
+ (None, "TWITTER_ACCESS_TOKEN_SECRET"),
199
+ ]
194
200
  )
195
201
  def get_my_user_profile() -> str:
196
202
  r"""Retrieves the authenticated user's Twitter profile info.
@@ -214,10 +220,12 @@ def get_my_user_profile() -> str:
214
220
 
215
221
 
216
222
  @api_keys_required(
217
- "TWITTER_CONSUMER_KEY",
218
- "TWITTER_CONSUMER_SECRET",
219
- "TWITTER_ACCESS_TOKEN",
220
- "TWITTER_ACCESS_TOKEN_SECRET",
223
+ [
224
+ (None, "TWITTER_CONSUMER_KEY"),
225
+ (None, "TWITTER_CONSUMER_SECRET"),
226
+ (None, "TWITTER_ACCESS_TOKEN"),
227
+ (None, "TWITTER_ACCESS_TOKEN_SECRET"),
228
+ ]
221
229
  )
222
230
  def get_user_by_username(username: str) -> str:
223
231
  r"""Retrieves one user's Twitter profile info by username (handle).
camel/types/__init__.py CHANGED
@@ -33,10 +33,11 @@ from .openai_types import (
33
33
  ChatCompletion,
34
34
  ChatCompletionAssistantMessageParam,
35
35
  ChatCompletionChunk,
36
- ChatCompletionFunctionMessageParam,
37
36
  ChatCompletionMessage,
38
37
  ChatCompletionMessageParam,
38
+ ChatCompletionMessageToolCall,
39
39
  ChatCompletionSystemMessageParam,
40
+ ChatCompletionToolMessageParam,
40
41
  ChatCompletionUserMessageParam,
41
42
  Choice,
42
43
  CompletionUsage,
@@ -62,7 +63,8 @@ __all__ = [
62
63
  'ChatCompletionSystemMessageParam',
63
64
  'ChatCompletionUserMessageParam',
64
65
  'ChatCompletionAssistantMessageParam',
65
- 'ChatCompletionFunctionMessageParam',
66
+ 'ChatCompletionToolMessageParam',
67
+ 'ChatCompletionMessageToolCall',
66
68
  'CompletionUsage',
67
69
  'OpenAIImageType',
68
70
  'OpenAIVisionDetailType',
camel/types/enums.py CHANGED
@@ -142,6 +142,12 @@ class ModelType(UnifiedModelType, Enum):
142
142
  # DeepSeek models
143
143
  DEEPSEEK_CHAT = "deepseek-chat"
144
144
 
145
+ # InternLM models
146
+ INTERNLM3_LATEST = "internlm3-latest"
147
+ INTERNLM3_8B_INSTRUCT = "internlm3-8b-instruct"
148
+ INTERNLM2_5_LATEST = "internlm2.5-latest"
149
+ INTERNLM2_PRO_CHAT = "internlm2-pro-chat"
150
+
145
151
  def __str__(self):
146
152
  return self.value
147
153
 
@@ -161,7 +167,15 @@ class ModelType(UnifiedModelType, Enum):
161
167
  @property
162
168
  def support_native_tool_calling(self) -> bool:
163
169
  return any(
164
- [self.is_openai, self.is_gemini, self.is_mistral, self.is_qwen]
170
+ [
171
+ self.is_openai,
172
+ self.is_gemini,
173
+ self.is_mistral,
174
+ self.is_qwen,
175
+ self.is_deepseek,
176
+ self.is_cohere,
177
+ self.is_internlm,
178
+ ]
165
179
  )
166
180
 
167
181
  @property
@@ -353,6 +367,15 @@ class ModelType(UnifiedModelType, Enum):
353
367
  ModelType.DEEPSEEK_CHAT,
354
368
  }
355
369
 
370
+ @property
371
+ def is_internlm(self) -> bool:
372
+ return self in {
373
+ ModelType.INTERNLM3_LATEST,
374
+ ModelType.INTERNLM3_8B_INSTRUCT,
375
+ ModelType.INTERNLM2_5_LATEST,
376
+ ModelType.INTERNLM2_PRO_CHAT,
377
+ }
378
+
356
379
  @property
357
380
  def token_limit(self) -> int:
358
381
  r"""Returns the maximum token limit for a given model.
@@ -411,6 +434,10 @@ class ModelType(UnifiedModelType, Enum):
411
434
  ModelType.NVIDIA_MISTRAL_LARGE,
412
435
  ModelType.NVIDIA_MIXTRAL_8X7B,
413
436
  ModelType.QWEN_QWQ_32B,
437
+ ModelType.INTERNLM3_8B_INSTRUCT,
438
+ ModelType.INTERNLM3_LATEST,
439
+ ModelType.INTERNLM2_5_LATEST,
440
+ ModelType.INTERNLM2_PRO_CHAT,
414
441
  }:
415
442
  return 32_768
416
443
  elif self in {
@@ -634,6 +661,7 @@ class ModelPlatformType(Enum):
634
661
  NVIDIA = "nvidia"
635
662
  DEEPSEEK = "deepseek"
636
663
  SGLANG = "sglang"
664
+ INTERNLM = "internlm"
637
665
 
638
666
  @property
639
667
  def is_openai(self) -> bool:
@@ -736,6 +764,11 @@ class ModelPlatformType(Enum):
736
764
  r"""Returns whether this platform is DeepSeek."""
737
765
  return self is ModelPlatformType.DEEPSEEK
738
766
 
767
+ @property
768
+ def is_internlm(self) -> bool:
769
+ r"""Returns whether this platform is InternLM."""
770
+ return self is ModelPlatformType.INTERNLM
771
+
739
772
 
740
773
  class AudioModelType(Enum):
741
774
  TTS_1 = "tts-1"
@@ -16,10 +16,10 @@ from openai.types.chat.chat_completion import ChatCompletion, Choice
16
16
  from openai.types.chat.chat_completion_assistant_message_param import (
17
17
  ChatCompletionAssistantMessageParam,
18
18
  )
19
- from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
20
- from openai.types.chat.chat_completion_function_message_param import (
21
- ChatCompletionFunctionMessageParam,
19
+ from openai.types.chat.chat_completion_tool_message_param import (
20
+ ChatCompletionToolMessageParam,
22
21
  )
22
+ from openai.types.chat.chat_completion_chunk import ChatCompletionChunk
23
23
  from openai.types.chat.chat_completion_message import ChatCompletionMessage
24
24
  from openai.types.chat.chat_completion_message_param import (
25
25
  ChatCompletionMessageParam,
@@ -33,6 +33,7 @@ from openai.types.chat.chat_completion_user_message_param import (
33
33
  from openai.types.completion_usage import CompletionUsage
34
34
  from openai.types.chat import ParsedChatCompletion
35
35
  from openai._types import NOT_GIVEN, NotGiven
36
+ from openai.types.chat import ChatCompletionMessageToolCall
36
37
 
37
38
  Choice = Choice
38
39
  ChatCompletion = ChatCompletion
@@ -42,7 +43,8 @@ ChatCompletionMessageParam = ChatCompletionMessageParam
42
43
  ChatCompletionSystemMessageParam = ChatCompletionSystemMessageParam
43
44
  ChatCompletionUserMessageParam = ChatCompletionUserMessageParam
44
45
  ChatCompletionAssistantMessageParam = ChatCompletionAssistantMessageParam
45
- ChatCompletionFunctionMessageParam = ChatCompletionFunctionMessageParam
46
+ ChatCompletionToolMessageParam = ChatCompletionToolMessageParam
47
+ ChatCompletionMessageToolCall = ChatCompletionMessageToolCall
46
48
  CompletionUsage = CompletionUsage
47
49
  NOT_GIVEN = NOT_GIVEN
48
50
  NotGiven = NotGiven
@@ -113,6 +113,11 @@ class UnifiedModelType(str):
113
113
  r"""Returns whether the model is a Qwen model."""
114
114
  return True
115
115
 
116
+ @property
117
+ def is_internlm(self) -> bool:
118
+ r"""Returns whether the model is a InternLM model."""
119
+ return True
120
+
116
121
  @property
117
122
  def support_native_structured_output(self) -> bool:
118
123
  r"""Returns whether the model supports native structured output."""
camel/utils/__init__.py CHANGED
@@ -19,6 +19,7 @@ from .commons import (
19
19
  check_server_running,
20
20
  create_chunks,
21
21
  dependencies_required,
22
+ download_github_subdirectory,
22
23
  download_tasks,
23
24
  func_string_to_callable,
24
25
  generate_prompt_for_structured_output,
@@ -79,5 +80,6 @@ __all__ = [
79
80
  "track_agent",
80
81
  "handle_http_error",
81
82
  "get_pydantic_model",
83
+ "download_github_subdirectory",
82
84
  "generate_prompt_for_structured_output",
83
85
  ]
camel/utils/commons.py CHANGED
@@ -21,6 +21,7 @@ import time
21
21
  import zipfile
22
22
  from functools import wraps
23
23
  from http import HTTPStatus
24
+ from pathlib import Path
24
25
  from typing import (
25
26
  Any,
26
27
  Callable,
@@ -29,6 +30,7 @@ from typing import (
29
30
  Mapping,
30
31
  Optional,
31
32
  Set,
33
+ Tuple,
32
34
  Type,
33
35
  TypeVar,
34
36
  cast,
@@ -231,41 +233,79 @@ def is_module_available(module_name: str) -> bool:
231
233
  return False
232
234
 
233
235
 
234
- def api_keys_required(*required_keys: str) -> Callable[[F], F]:
235
- r"""A decorator to check if the required API keys are
236
- presented in the environment variables or as an instance attribute.
236
+ def api_keys_required(
237
+ param_env_list: List[Tuple[Optional[str], str]],
238
+ ) -> Callable[[F], F]:
239
+ r"""A decorator to check if the required API keys are provided in the
240
+ environment variables or as function arguments.
237
241
 
238
242
  Args:
239
- required_keys (str): The required API keys to be checked.
243
+ param_env_list (List[Tuple[Optional[str], str]]): A list of tuples
244
+ where each tuple contains a function argument name (as the first
245
+ element, or None) and the corresponding environment variable name
246
+ (as the second element) that holds the API key.
240
247
 
241
248
  Returns:
242
- Callable[[F], F]: The original function with the added check
243
- for required API keys.
249
+ Callable[[F], F]: The original function wrapped with the added check
250
+ for the required API keys.
244
251
 
245
252
  Raises:
246
- ValueError: If any of the required API keys are missing in the
247
- environment variables and the instance attribute.
253
+ ValueError: If any of the required API keys are missing, either
254
+ from the function arguments or environment variables.
248
255
 
249
256
  Example:
250
257
  ::
251
258
 
252
- @api_keys_required('API_KEY_1', 'API_KEY_2')
253
- def some_api_function():
254
- # Function implementation...
259
+ @api_keys_required([
260
+ ('api_key_arg', 'API_KEY_1'),
261
+ ('another_key_arg', 'API_KEY_2'),
262
+ (None, 'API_KEY_3'),
263
+ ])
264
+ def some_api_function(api_key_arg=None, another_key_arg=None):
265
+ # Function implementation that requires API keys
255
266
  """
267
+ import inspect
256
268
 
257
269
  def decorator(func: F) -> F:
258
270
  @wraps(func)
259
271
  def wrapper(*args: Any, **kwargs: Any) -> Any:
260
- missing_environment_keys = [
261
- k for k in required_keys if k not in os.environ
262
- ]
263
- if (
264
- not (args and getattr(args[0], '_api_key', None))
265
- and missing_environment_keys
266
- ):
272
+ signature = inspect.signature(func)
273
+ bound_arguments = signature.bind(*args, **kwargs)
274
+ bound_arguments.apply_defaults()
275
+ arguments = bound_arguments.arguments
276
+
277
+ missing_keys = []
278
+ for param_name, env_var_name in param_env_list:
279
+ if not isinstance(env_var_name, str):
280
+ raise TypeError(
281
+ f"Environment variable name must be a string, got"
282
+ f" {type(env_var_name)}"
283
+ )
284
+
285
+ value = None
286
+ if (
287
+ param_name
288
+ ): # If param_name is provided, check function argument first
289
+ if not isinstance(param_name, str):
290
+ raise TypeError(
291
+ f"Parameter name must be a string, "
292
+ f"got {type(param_name)}"
293
+ )
294
+ value = arguments.get(param_name)
295
+ # If we found a valid value in arguments, continue to next
296
+ # item
297
+ if value:
298
+ continue
299
+
300
+ # Check environment variable if no valid value found yet
301
+ value = os.environ.get(env_var_name)
302
+ if not value or value.strip() == "":
303
+ missing_keys.append(env_var_name)
304
+
305
+ if missing_keys:
267
306
  raise ValueError(
268
- f"Missing API keys: {', '.join(missing_environment_keys)}"
307
+ "Missing or empty required API keys in "
308
+ f"environment variables: {', '.join(missing_keys)}"
269
309
  )
270
310
  return func(*args, **kwargs)
271
311
 
@@ -609,6 +649,51 @@ def retry_request(
609
649
  raise
610
650
 
611
651
 
652
+ def download_github_subdirectory(
653
+ repo: str, subdir: str, data_dir: Path, branch="main"
654
+ ):
655
+ r"""Download subdirectory of the Github repo of
656
+ the benchmark.
657
+
658
+ This function downloads all files and subdirectories from a
659
+ specified subdirectory of a GitHub repository and
660
+ saves them to a local directory.
661
+
662
+ Args:
663
+ repo (str): The name of the GitHub repository
664
+ in the format "owner/repo".
665
+ subdir (str): The path to the subdirectory
666
+ within the repository to download.
667
+ data_dir (Path): The local directory where
668
+ the files will be saved.
669
+ branch (str, optional): The branch of the repository to use.
670
+ Defaults to "main".
671
+ """
672
+ from tqdm import tqdm
673
+
674
+ api_url = (
675
+ f"https://api.github.com/repos/{repo}/contents/{subdir}?ref={branch}"
676
+ )
677
+ headers = {"Accept": "application/vnd.github.v3+json"}
678
+ response = requests.get(api_url, headers=headers)
679
+ response.raise_for_status()
680
+ files = response.json()
681
+ os.makedirs(data_dir, exist_ok=True)
682
+
683
+ for file in tqdm(files, desc="Downloading"):
684
+ file_path = data_dir / file["name"]
685
+
686
+ if file["type"] == "file":
687
+ file_url = file["download_url"]
688
+ file_response = requests.get(file_url)
689
+ with open(file_path, "wb") as f:
690
+ f.write(file_response.content)
691
+ elif file["type"] == "dir":
692
+ download_github_subdirectory(
693
+ repo, f'{subdir}/{file["name"]}', file_path, branch
694
+ )
695
+
696
+
612
697
  def generate_prompt_for_structured_output(
613
698
  response_format: Optional[Type[BaseModel]],
614
699
  user_message: str,
@@ -253,11 +253,11 @@ class AnthropicTokenCounter(BaseTokenCounter):
253
253
  Returns:
254
254
  int: Number of tokens in the messages.
255
255
  """
256
- from anthropic.types.beta import BetaMessageParam
256
+ from anthropic.types import MessageParam
257
257
 
258
- return self.client.beta.messages.count_tokens(
258
+ return self.client.messages.count_tokens(
259
259
  messages=[
260
- BetaMessageParam(
260
+ MessageParam(
261
261
  content=str(msg["content"]),
262
262
  role="user" if msg["role"] == "user" else "assistant",
263
263
  )