camel-ai 0.2.37__py3-none-any.whl → 0.2.39__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 (122) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +4 -0
  3. camel/agents/repo_agent.py +2 -2
  4. camel/benchmarks/apibank.py +1 -1
  5. camel/benchmarks/apibench.py +1 -1
  6. camel/configs/__init__.py +3 -0
  7. camel/configs/modelscope_config.py +59 -0
  8. camel/datagen/evol_instruct/__init__.py +20 -0
  9. camel/datagen/evol_instruct/evol_instruct.py +424 -0
  10. camel/datagen/evol_instruct/scorer.py +166 -0
  11. camel/datagen/evol_instruct/templates.py +268 -0
  12. camel/datagen/self_improving_cot.py +1 -1
  13. camel/datasets/__init__.py +2 -0
  14. camel/datasets/base_generator.py +22 -9
  15. camel/datasets/few_shot_generator.py +2 -3
  16. camel/datasets/self_instruct_generator.py +415 -0
  17. camel/embeddings/openai_compatible_embedding.py +13 -5
  18. camel/environments/models.py +10 -4
  19. camel/environments/single_step.py +181 -41
  20. camel/interpreters/docker_interpreter.py +2 -2
  21. camel/interpreters/e2b_interpreter.py +1 -1
  22. camel/interpreters/internal_python_interpreter.py +1 -1
  23. camel/interpreters/subprocess_interpreter.py +1 -1
  24. camel/loaders/__init__.py +2 -2
  25. camel/loaders/{panda_reader.py → pandas_reader.py} +61 -30
  26. camel/loaders/unstructured_io.py +2 -1
  27. camel/memories/blocks/chat_history_block.py +1 -1
  28. camel/memories/context_creators/score_based.py +198 -67
  29. camel/models/__init__.py +2 -0
  30. camel/models/aiml_model.py +9 -3
  31. camel/models/anthropic_model.py +11 -3
  32. camel/models/azure_openai_model.py +9 -3
  33. camel/models/base_audio_model.py +6 -0
  34. camel/models/base_model.py +4 -0
  35. camel/models/deepseek_model.py +9 -3
  36. camel/models/gemini_model.py +9 -3
  37. camel/models/groq_model.py +9 -3
  38. camel/models/internlm_model.py +8 -2
  39. camel/models/model_factory.py +123 -0
  40. camel/models/modelscope_model.py +208 -0
  41. camel/models/moonshot_model.py +8 -2
  42. camel/models/nemotron_model.py +9 -3
  43. camel/models/nvidia_model.py +9 -3
  44. camel/models/ollama_model.py +9 -3
  45. camel/models/openai_audio_models.py +7 -5
  46. camel/models/openai_compatible_model.py +9 -3
  47. camel/models/openai_model.py +58 -5
  48. camel/models/openrouter_model.py +9 -3
  49. camel/models/qwen_model.py +9 -3
  50. camel/models/samba_model.py +9 -3
  51. camel/models/sglang_model.py +11 -4
  52. camel/models/siliconflow_model.py +8 -2
  53. camel/models/stub_model.py +2 -1
  54. camel/models/togetherai_model.py +11 -5
  55. camel/models/vllm_model.py +10 -4
  56. camel/models/yi_model.py +9 -3
  57. camel/models/zhipuai_model.py +11 -5
  58. camel/retrievers/auto_retriever.py +14 -0
  59. camel/retrievers/vector_retriever.py +1 -1
  60. camel/storages/__init__.py +2 -0
  61. camel/storages/graph_storages/neo4j_graph.py +1 -1
  62. camel/storages/vectordb_storages/__init__.py +2 -0
  63. camel/storages/vectordb_storages/base.py +2 -2
  64. camel/storages/vectordb_storages/milvus.py +2 -2
  65. camel/storages/vectordb_storages/qdrant.py +2 -2
  66. camel/storages/vectordb_storages/tidb.py +332 -0
  67. camel/tasks/task.py +2 -2
  68. camel/toolkits/__init__.py +9 -1
  69. camel/toolkits/arxiv_toolkit.py +2 -1
  70. camel/toolkits/ask_news_toolkit.py +11 -3
  71. camel/toolkits/audio_analysis_toolkit.py +2 -0
  72. camel/toolkits/base.py +3 -0
  73. camel/toolkits/browser_toolkit.py +84 -61
  74. camel/toolkits/code_execution.py +3 -1
  75. camel/toolkits/dappier_toolkit.py +2 -1
  76. camel/toolkits/data_commons_toolkit.py +2 -0
  77. camel/toolkits/excel_toolkit.py +2 -0
  78. camel/toolkits/file_write_toolkit.py +2 -0
  79. camel/toolkits/github_toolkit.py +6 -4
  80. camel/toolkits/google_scholar_toolkit.py +2 -0
  81. camel/toolkits/human_toolkit.py +17 -1
  82. camel/toolkits/image_analysis_toolkit.py +2 -0
  83. camel/toolkits/linkedin_toolkit.py +2 -1
  84. camel/toolkits/math_toolkit.py +2 -0
  85. camel/toolkits/mcp_toolkit.py +42 -52
  86. camel/toolkits/meshy_toolkit.py +20 -2
  87. camel/toolkits/networkx_toolkit.py +2 -0
  88. camel/toolkits/notion_toolkit.py +7 -0
  89. camel/toolkits/openai_agent_toolkit.py +131 -0
  90. camel/toolkits/openbb_toolkit.py +2 -1
  91. camel/toolkits/pubmed_toolkit.py +2 -0
  92. camel/toolkits/reddit_toolkit.py +2 -1
  93. camel/toolkits/retrieval_toolkit.py +2 -1
  94. camel/toolkits/search_toolkit.py +2 -1
  95. camel/toolkits/searxng_toolkit.py +207 -0
  96. camel/toolkits/semantic_scholar_toolkit.py +2 -0
  97. camel/toolkits/slack_toolkit.py +2 -0
  98. camel/toolkits/stripe_toolkit.py +2 -1
  99. camel/toolkits/sympy_toolkit.py +2 -0
  100. camel/toolkits/terminal_toolkit.py +2 -0
  101. camel/toolkits/thinking_toolkit.py +168 -12
  102. camel/toolkits/twitter_toolkit.py +2 -1
  103. camel/toolkits/video_analysis_toolkit.py +2 -1
  104. camel/toolkits/video_download_toolkit.py +2 -1
  105. camel/toolkits/weather_toolkit.py +2 -0
  106. camel/toolkits/whatsapp_toolkit.py +2 -1
  107. camel/toolkits/zapier_toolkit.py +2 -1
  108. camel/types/enums.py +66 -0
  109. camel/types/unified_model_type.py +5 -0
  110. camel/utils/__init__.py +2 -0
  111. camel/utils/chunker/code_chunker.py +9 -9
  112. camel/utils/commons.py +50 -30
  113. camel/utils/constants.py +2 -2
  114. camel/utils/mcp.py +79 -0
  115. camel/verifiers/__init__.py +2 -0
  116. camel/verifiers/base.py +15 -15
  117. camel/verifiers/math_verifier.py +182 -0
  118. camel/verifiers/python_verifier.py +28 -28
  119. {camel_ai-0.2.37.dist-info → camel_ai-0.2.39.dist-info}/METADATA +54 -4
  120. {camel_ai-0.2.37.dist-info → camel_ai-0.2.39.dist-info}/RECORD +122 -110
  121. {camel_ai-0.2.37.dist-info → camel_ai-0.2.39.dist-info}/WHEEL +0 -0
  122. {camel_ai-0.2.37.dist-info → camel_ai-0.2.39.dist-info}/licenses/LICENSE +0 -0
camel/__init__.py CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  from camel.logger import disable_logging, enable_logging, set_log_level
16
16
 
17
- __version__ = '0.2.37'
17
+ __version__ = '0.2.39'
18
18
 
19
19
  __all__ = [
20
20
  '__version__',
@@ -526,6 +526,10 @@ class ChatAgent(BaseAgent):
526
526
  message.content = response.output_messages[0].content
527
527
  if not self._try_format_message(message, response_format):
528
528
  logger.warning(f"Failed to parse response: {message.content}")
529
+ logger.warning(
530
+ "To improve reliability, consider using models "
531
+ "that are better equipped to handle structured output"
532
+ )
529
533
 
530
534
  async def _aformat_response_if_needed(
531
535
  self,
@@ -17,7 +17,7 @@ from string import Template
17
17
  from typing import TYPE_CHECKING, List, Optional, Tuple, Union
18
18
 
19
19
  if TYPE_CHECKING:
20
- from github import Github
20
+ from github.MainClass import Github
21
21
  from pydantic import BaseModel
22
22
 
23
23
  from camel.agents import ChatAgent
@@ -219,7 +219,7 @@ class RepoAgent(ChatAgent):
219
219
  List[RepositoryInfo]: A list of objects containing information
220
220
  about the all repositories, including the contents.
221
221
  """
222
- from github import Github
222
+ from github.MainClass import Github
223
223
 
224
224
  github_client = Github(self.github_auth_token)
225
225
  res = []
@@ -48,7 +48,7 @@ def process_messages(
48
48
  Args:
49
49
  chat_history (List[Dict[str, Any]):
50
50
  A list of dictionaries representing the chat history.
51
- prompt (str): A propmt to be set as the system message.
51
+ prompt (str): A prompt to be set as the system message.
52
52
 
53
53
  Returns:
54
54
  List[Dict[str, str]]: A list of dictionaries representing
@@ -30,7 +30,7 @@ logger = logging.getLogger(__name__)
30
30
 
31
31
 
32
32
  # Mapping of dataset names to file names
33
- # 'Oracle' retriver used here which means all the full
33
+ # 'Oracle' retriever used here which means all the full
34
34
  # API documentation will be included in the prompt
35
35
  dataset_mapping = {
36
36
  "huggingface": {
camel/configs/__init__.py CHANGED
@@ -21,6 +21,7 @@ from .groq_config import GROQ_API_PARAMS, GroqConfig
21
21
  from .internlm_config import INTERNLM_API_PARAMS, InternLMConfig
22
22
  from .litellm_config import LITELLM_API_PARAMS, LiteLLMConfig
23
23
  from .mistral_config import MISTRAL_API_PARAMS, MistralConfig
24
+ from .modelscope_config import MODELSCOPE_API_PARAMS, ModelScopeConfig
24
25
  from .moonshot_config import MOONSHOT_API_PARAMS, MoonshotConfig
25
26
  from .nvidia_config import NVIDIA_API_PARAMS, NvidiaConfig
26
27
  from .ollama_config import OLLAMA_API_PARAMS, OllamaConfig
@@ -85,6 +86,8 @@ __all__ = [
85
86
  'INTERNLM_API_PARAMS',
86
87
  'MoonshotConfig',
87
88
  "MOONSHOT_API_PARAMS",
89
+ 'ModelScopeConfig',
90
+ 'MODELSCOPE_API_PARAMS',
88
91
  'SiliconFlowConfig',
89
92
  'SILICONFLOW_API_PARAMS',
90
93
  'AIMLConfig',
@@ -0,0 +1,59 @@
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 Optional, Union
17
+
18
+ from camel.configs.base_config import BaseConfig
19
+
20
+
21
+ class ModelScopeConfig(BaseConfig):
22
+ r"""Defines the parameters for generating chat completions using the
23
+ ModelScope API. You can refer to the following link for more details:
24
+ https://www.modelscope.cn/docs/model-service/API-Inference/intro
25
+
26
+ Args:
27
+ tool_choice (Union[dict[str, str], str], optional): Controls which (if
28
+ any) tool is called by the model. :obj:`"none"` means the model
29
+ will not call any tool and instead generates a message.
30
+ :obj:`"auto"` means the model can pick between generating a
31
+ message or calling one or more tools. :obj:`"required"` or
32
+ specifying a particular tool via
33
+ {"type": "function", "function": {"name": "some_function"}}
34
+ can be used to guide the model to use tools more strongly.
35
+ (default: :obj:`None`)
36
+ max_tokens (int, optional): Specifies the maximum number of tokens
37
+ the model can generate. This sets an upper limit, but does not
38
+ guarantee that this number will always be reached.
39
+ (default: :obj:`None`)
40
+ top_p (float, optional): Controls the randomness of the generated
41
+ results. Lower values lead to less randomness, while higher
42
+ values increase randomness. (default: :obj:`None`)
43
+ temperature (float, optional): Controls the diversity and focus of
44
+ the generated results. Lower values make the output more focused,
45
+ while higher values make it more diverse. (default: :obj:`0.3`)
46
+ stream (bool, optional): If True, enables streaming output.
47
+ (default: :obj:`None`)
48
+ """
49
+
50
+ tool_choice: Optional[Union[dict[str, str], str]] = None
51
+ max_tokens: Optional[int] = None
52
+ top_p: Optional[float] = None
53
+ temperature: Optional[float] = None
54
+ stream: Optional[bool] = None
55
+
56
+
57
+ MODELSCOPE_API_PARAMS = {
58
+ param for param in ModelScopeConfig.model_fields.keys()
59
+ }
@@ -0,0 +1,20 @@
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
+
15
+ from .evol_instruct import EvolInstructPipeline
16
+
17
+ __all__ = [
18
+ 'EvolInstructPipeline',
19
+ 'MathEvolInstructTemplates',
20
+ ]
@@ -0,0 +1,424 @@
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
+
15
+ import random
16
+ import time
17
+ from concurrent.futures import ThreadPoolExecutor
18
+ from math import ceil
19
+ from typing import Any, Dict, List, Optional, Tuple, Type, Union, cast
20
+
21
+ from tqdm import tqdm
22
+
23
+ from camel.agents import ChatAgent
24
+ from camel.datagen.evol_instruct.scorer import BaseScorer, GeneralScorer
25
+ from camel.datagen.evol_instruct.templates import EvolInstructTemplates
26
+ from camel.logger import get_logger
27
+
28
+ logger = get_logger(__name__)
29
+
30
+
31
+ class EvolInstructPipeline:
32
+ r"""Pipeline for evolving prompts using the Evol-Instruct methodology.
33
+
34
+ Supports custom templates defining evolution strategies and methods. The
35
+ pipeline leverages language models to iteratively refine prompts through
36
+ specified evolution strategies.
37
+
38
+ Args:
39
+ templates (Type[EvolInstructTemplates]): Template class containing
40
+ evolution strategy and method definitions. Must provide
41
+ `EVOL_METHODS` and `STRATEGY` attributes.
42
+ (default: :obj:`EvolInstructTemplates`)
43
+ agent (Optional[ChatAgent]): Chat agent instance for LLM interaction.
44
+ If :obj:`None`, initializes with a default ChatAgent.
45
+ (default: :obj:`None`)
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ templates: Type = EvolInstructTemplates,
51
+ agent: Optional[ChatAgent] = None,
52
+ ) -> None:
53
+ r"""Initialize pipeline with templates and language model agent.
54
+
55
+ Args:
56
+ templates (Type[EvolInstructTemplates]): Template class containing
57
+ evolution strategy configurations.
58
+ (default: :obj:`EvolInstructTemplates`)
59
+ agent (Optional[ChatAgent]): Preconfigured chat agent instance.
60
+ Creates a default ChatAgent if not provided.
61
+ (default: :obj:`None`)
62
+ """
63
+ self.templates = templates
64
+ self.agent = agent or ChatAgent()
65
+
66
+ def _resolve_evolution_method(self, method_key: str) -> str:
67
+ r"""Resolve evolution method key to concrete implementation.
68
+
69
+ Args:
70
+ method_key (str): Input method identifier. Can be:
71
+ - Direct method key from templates.EVOL_METHODS
72
+ - Strategy name from templates.STRATEGY keys
73
+
74
+ Returns:
75
+ str: Resolved method key from EVOL_METHODS
76
+ """
77
+ if method_key in self.templates.EVOL_METHODS:
78
+ return method_key
79
+ if method_key.upper() in self.templates.STRATEGY:
80
+ strategy = self.templates.STRATEGY[method_key.upper()]
81
+ strategy_methods = strategy["methods"]
82
+ return random.choice(strategy_methods)
83
+
84
+ logger.warning(
85
+ f"Invalid evolution method: {method_key}. "
86
+ f"Using random selection."
87
+ )
88
+ return random.choice(list(self.templates.EVOL_METHODS))
89
+
90
+ def _get_evolution_methods(
91
+ self,
92
+ method: Union[str, List[str]],
93
+ num_generations: int = 2,
94
+ ) -> List[str]:
95
+ r"""Get list of evolution methods based on input specification.
96
+
97
+ Args:
98
+ method (Union[str, List[str]]): Specification for method selection.
99
+ Can be:
100
+ - Strategy name for methods from that strategy
101
+ - Specific method name
102
+ - List of method specifications
103
+ num_generations (int): Number of methods to return.
104
+
105
+ Returns:
106
+ List[str]: List of resolved method names
107
+ """
108
+ candidate_methods = []
109
+
110
+ if isinstance(method, list):
111
+ for method_spec in method:
112
+ candidate_methods.append(
113
+ self._resolve_evolution_method(method_spec)
114
+ )
115
+ elif isinstance(method, str):
116
+ if method.upper() in self.templates.STRATEGY:
117
+ strategy = self.templates.STRATEGY[method.upper()]
118
+ candidate_methods = strategy["methods"]
119
+ else:
120
+ candidate_methods = [self._resolve_evolution_method(method)]
121
+
122
+ # Remove duplicates while preserving order
123
+ unique_candidates = []
124
+ for method_name in candidate_methods:
125
+ if method_name not in unique_candidates:
126
+ unique_candidates.append(method_name)
127
+
128
+ if len(unique_candidates) >= num_generations:
129
+ methods = random.sample(unique_candidates, num_generations)
130
+ else:
131
+ methods = unique_candidates.copy()
132
+ while len(methods) < num_generations:
133
+ methods.append(random.choice(unique_candidates))
134
+
135
+ return methods
136
+
137
+ def _generate_single_evolution(
138
+ self,
139
+ prompt: str,
140
+ method: str,
141
+ return_method: bool = False,
142
+ ) -> Tuple[str, str]:
143
+ r"""Generate a single evolved prompt from a seed prompt.
144
+
145
+ Args:
146
+ prompt (str): The seed prompt to evolve.
147
+ method (str): The evolution method key to use.
148
+ return_method (bool): If True, returns method along with prompt.
149
+
150
+ Returns:
151
+ Tuple[str, str]: Evolved prompt and method
152
+ """
153
+ resolved_method = self._resolve_evolution_method(method)
154
+
155
+ # Find strategy containing the resolved method
156
+ strategy_key = None
157
+ for strategy, group in self.templates.STRATEGY.items():
158
+ if resolved_method in group["methods"]:
159
+ strategy_key = strategy
160
+ break
161
+
162
+ if strategy_key is None:
163
+ strategy_key = random.choice(list(self.templates.STRATEGY.keys()))
164
+
165
+ strategy = self.templates.STRATEGY[strategy_key]
166
+ instruction_template = strategy["meta_instruction"]
167
+ instruction = instruction_template.format(
168
+ method=self.templates.EVOL_METHODS.get(
169
+ resolved_method,
170
+ random.choice(list(self.templates.EVOL_METHODS.values())),
171
+ ),
172
+ prompt=prompt,
173
+ )
174
+
175
+ self.agent.reset()
176
+ response = self.agent.step(instruction)
177
+ evolved_prompt = response.msgs[0].content.strip()
178
+
179
+ if return_method:
180
+ return (evolved_prompt, resolved_method)
181
+ else:
182
+ return (evolved_prompt, "")
183
+
184
+ def _generate_multiple_evolutions(
185
+ self,
186
+ prompt: str,
187
+ method: Union[str, List[str]],
188
+ num_generations: int = 2,
189
+ keep_original: bool = True,
190
+ num_threads: int = 10,
191
+ ) -> List[Tuple[str, str]]:
192
+ r"""Generate multiple evolved versions of a prompt.
193
+
194
+ Args:
195
+ prompt (str): Seed prompt to evolve.
196
+ method (Union[str, List[str]]): Evolution method specification.
197
+ num_generations (int): Candidates to generate per iteration.
198
+ keep_original (bool): Whether to keep the original prompt.
199
+ num_threads (int): Number of threads for parallel processing.
200
+
201
+ Returns:
202
+ List[Tuple[str, str]]: List of (evolved_prompt, method) pairs
203
+ """
204
+ results = [(prompt, "original")] if keep_original else []
205
+
206
+ if isinstance(method, list) and len(method) == num_generations:
207
+ candidate_methods = method
208
+ else:
209
+ candidate_methods = self._get_evolution_methods(
210
+ method=method, num_generations=num_generations
211
+ )
212
+
213
+ def _process_single_method(method_name: str) -> Tuple[str, str]:
214
+ return self._generate_single_evolution(
215
+ prompt, method_name, return_method=True
216
+ )
217
+
218
+ with ThreadPoolExecutor(max_workers=num_threads) as executor:
219
+ evolved_results = list(
220
+ executor.map(_process_single_method, candidate_methods)
221
+ )
222
+
223
+ results.extend(evolved_results)
224
+ return results
225
+
226
+ def _generate_iterative_evolutions(
227
+ self,
228
+ prompt: str,
229
+ evolution_spec: Union[str, List[Union[str, List[str]]]],
230
+ num_generations: int = 2,
231
+ num_iterations: Optional[int] = None,
232
+ keep_original: bool = True,
233
+ scorer: Optional[BaseScorer] = None,
234
+ num_threads: int = 10,
235
+ ) -> Dict[int, List[Dict[str, Any]]]:
236
+ r"""Generate iterative evolutions of a prompt with scoring.
237
+
238
+ Args:
239
+ prompt (str): Seed prompt to evolve.
240
+ evolution_spec (Union[str, List[Union[str, List[str]]]]):
241
+ Evolution method specification.
242
+ If a list is provided and num_iterations is None, then
243
+ num_iterations is set to the length of the list.
244
+ num_generations (int): Candidates to generate per iteration.
245
+ num_iterations (Optional[int]): Number of evolution iterations.
246
+ Defaults to the length of evolution_spec.
247
+ keep_original (bool): Include original prompt in results.
248
+ scorer (Optional[BaseScorer]): Scoring model for candidate.
249
+ num_threads (int): Number of threads for parallel processing.
250
+
251
+ Returns:
252
+ Dict[int, List[Dict[str, Any]]]: Evolution results per iteration,
253
+ where each candidate is represented as a dict with keys:
254
+ "instruction", "method", and "scores".
255
+ """
256
+ if num_iterations is None:
257
+ if isinstance(evolution_spec, list):
258
+ num_iterations = len(evolution_spec)
259
+ else:
260
+ num_iterations = 1
261
+
262
+ results = {}
263
+ current_prompt = prompt
264
+ scorer = scorer or GeneralScorer()
265
+
266
+ for iteration in range(num_iterations):
267
+ if isinstance(evolution_spec, list):
268
+ if iteration < len(evolution_spec):
269
+ iteration_spec = evolution_spec[iteration]
270
+ else:
271
+ iteration_spec = evolution_spec[-1]
272
+ else:
273
+ iteration_spec = evolution_spec
274
+
275
+ batch_results = self._generate_multiple_evolutions(
276
+ prompt=current_prompt,
277
+ method=iteration_spec,
278
+ num_generations=num_generations,
279
+ keep_original=False,
280
+ num_threads=num_threads,
281
+ )
282
+
283
+ scored_results = []
284
+ for candidate, method_used in batch_results:
285
+ scores = scorer.score(current_prompt, candidate)
286
+ scored_results.append(
287
+ {
288
+ "instruction": candidate,
289
+ "method": method_used,
290
+ "scores": scores,
291
+ }
292
+ )
293
+
294
+ best_index = max(
295
+ range(len(scored_results)),
296
+ key=lambda i: sum(
297
+ cast(Dict[str, int], scored_results[i]["scores"]).values()
298
+ ),
299
+ )
300
+
301
+ best_candidate = cast(
302
+ str, scored_results[best_index]["instruction"]
303
+ )
304
+
305
+ if keep_original:
306
+ results[iteration] = [
307
+ {
308
+ "instruction": current_prompt,
309
+ "method": "original",
310
+ "scores": {},
311
+ },
312
+ *scored_results,
313
+ ]
314
+ else:
315
+ results[iteration] = scored_results
316
+
317
+ current_prompt = best_candidate
318
+
319
+ return results
320
+
321
+ def generate(
322
+ self,
323
+ prompts: List[str],
324
+ evolution_spec: Union[str, List[Union[str, List[str]]]],
325
+ num_generations: int = 2,
326
+ num_iterations: Optional[int] = None,
327
+ keep_original: bool = True,
328
+ scorer: Optional[BaseScorer] = None,
329
+ num_chunks: int = 1,
330
+ retry_limit: int = 3,
331
+ retry_delay: float = 1.0,
332
+ num_threads: int = 10,
333
+ ) -> List[Dict[int, List[Dict[str, Any]]]]:
334
+ r"""Evolve a batch of prompts through iterative refinement.
335
+
336
+ Args:
337
+ prompts (List[str]): Seed prompts to evolve.
338
+ evolution_spec (Union[str, List[Union[str, List[str]]]]):
339
+ Evolution method specification.
340
+ If a list is provided and num_iterations is None, then
341
+ num_iterations is set to the length of the list.
342
+ num_generations (int): Candidates to generate per iteration.
343
+ num_iterations (Optional[int]): Number of evolution iterations.
344
+ Defaults to the length of evolution_spec.
345
+ keep_original (bool): Include original prompts in results.
346
+ scorer (Optional[BaseScorer]): Scoring model for candidate.
347
+ num_chunks (int): Number of parallel processing chunks.
348
+ retry_limit (int): Max retries for failed generations.
349
+ retry_delay (float): Delay between retries in seconds.
350
+ num_threads (int): Number of threads for parallel processing.
351
+
352
+ Returns:
353
+ List[Dict[int, List[Dict[str, Any]]]]: Evolution results.
354
+ """
355
+ if num_iterations is None:
356
+ if isinstance(evolution_spec, list):
357
+ num_iterations = len(evolution_spec)
358
+ else:
359
+ num_iterations = 1
360
+
361
+ evolution_plan: List[List[List[str]]] = []
362
+ for _ in prompts:
363
+ prompt_plan = []
364
+ for iteration in range(num_iterations):
365
+ if isinstance(evolution_spec, list):
366
+ if iteration < len(evolution_spec):
367
+ raw_spec = evolution_spec[iteration]
368
+ else:
369
+ raw_spec = evolution_spec[-1]
370
+ else:
371
+ raw_spec = evolution_spec
372
+ prompt_plan.append(
373
+ self._get_evolution_methods(raw_spec, num_generations)
374
+ )
375
+ evolution_plan.append(prompt_plan)
376
+
377
+ def _process_prompt(
378
+ args: Tuple[str, List[List[str]]],
379
+ ) -> Dict[int, List[Dict[str, Any]]]:
380
+ prompt, methods = args
381
+ retries = 0
382
+ while retries <= retry_limit:
383
+ try:
384
+ return self._generate_iterative_evolutions(
385
+ prompt=prompt,
386
+ evolution_spec=evolution_spec,
387
+ num_generations=num_generations,
388
+ num_iterations=num_iterations,
389
+ keep_original=keep_original,
390
+ scorer=scorer,
391
+ num_threads=num_threads,
392
+ )
393
+ except Exception as e:
394
+ retries += 1
395
+ if retries <= retry_limit:
396
+ logger.warning(
397
+ f"Error processing prompt "
398
+ f"(attempt {retries}/{retry_limit}): {e!s}"
399
+ )
400
+ time.sleep(retry_delay)
401
+ else:
402
+ logger.error("Failed to process prompt.")
403
+ return {}
404
+
405
+ raise RuntimeError("_process_prompt() did not return.")
406
+
407
+ num_chunks = max(1, min(num_chunks, len(prompts)))
408
+ chunk_size = ceil(len(prompts) / num_chunks)
409
+ results = []
410
+
411
+ for chunk_idx in range(0, len(prompts), chunk_size):
412
+ chunk = prompts[chunk_idx : chunk_idx + chunk_size]
413
+ plan_chunk = evolution_plan[chunk_idx : chunk_idx + chunk_size]
414
+
415
+ with ThreadPoolExecutor(max_workers=num_threads) as executor:
416
+ chunk_results = list(
417
+ tqdm(
418
+ executor.map(_process_prompt, zip(chunk, plan_chunk)),
419
+ total=len(chunk),
420
+ )
421
+ )
422
+ results.extend(chunk_results)
423
+
424
+ return results