camel-ai 0.2.21__py3-none-any.whl → 0.2.23a0__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/_types.py +41 -0
- camel/agents/_utils.py +188 -0
- camel/agents/chat_agent.py +556 -965
- camel/agents/knowledge_graph_agent.py +7 -1
- camel/agents/multi_hop_generator_agent.py +1 -1
- camel/configs/base_config.py +10 -13
- camel/configs/deepseek_config.py +4 -30
- camel/configs/gemini_config.py +5 -31
- camel/configs/openai_config.py +14 -32
- camel/configs/qwen_config.py +36 -36
- camel/datagen/self_improving_cot.py +79 -1
- camel/datagen/self_instruct/filter/instruction_filter.py +19 -3
- camel/datagen/self_instruct/self_instruct.py +7 -2
- camel/datasets/__init__.py +28 -0
- camel/datasets/base.py +969 -0
- camel/embeddings/openai_embedding.py +10 -1
- camel/environments/__init__.py +16 -0
- camel/environments/base.py +503 -0
- camel/extractors/__init__.py +16 -0
- camel/extractors/base.py +263 -0
- camel/interpreters/docker/Dockerfile +12 -0
- camel/interpreters/docker_interpreter.py +19 -1
- camel/interpreters/subprocess_interpreter.py +42 -17
- camel/loaders/__init__.py +2 -0
- camel/loaders/mineru_extractor.py +250 -0
- camel/memories/agent_memories.py +16 -1
- camel/memories/blocks/chat_history_block.py +10 -2
- camel/memories/blocks/vectordb_block.py +1 -0
- camel/memories/context_creators/score_based.py +20 -3
- camel/memories/records.py +10 -0
- camel/messages/base.py +8 -8
- camel/models/_utils.py +57 -0
- camel/models/aiml_model.py +48 -17
- camel/models/anthropic_model.py +41 -3
- camel/models/azure_openai_model.py +39 -3
- camel/models/base_model.py +132 -4
- camel/models/cohere_model.py +88 -11
- camel/models/deepseek_model.py +107 -63
- camel/models/gemini_model.py +133 -15
- camel/models/groq_model.py +72 -10
- camel/models/internlm_model.py +14 -3
- camel/models/litellm_model.py +9 -2
- camel/models/mistral_model.py +42 -5
- camel/models/model_manager.py +48 -3
- camel/models/moonshot_model.py +33 -4
- camel/models/nemotron_model.py +32 -3
- camel/models/nvidia_model.py +43 -3
- camel/models/ollama_model.py +139 -17
- camel/models/openai_audio_models.py +7 -1
- camel/models/openai_compatible_model.py +37 -3
- camel/models/openai_model.py +158 -46
- camel/models/qwen_model.py +61 -4
- camel/models/reka_model.py +53 -3
- camel/models/samba_model.py +209 -4
- camel/models/sglang_model.py +153 -14
- camel/models/siliconflow_model.py +16 -3
- camel/models/stub_model.py +46 -4
- camel/models/togetherai_model.py +38 -3
- camel/models/vllm_model.py +37 -3
- camel/models/yi_model.py +36 -3
- camel/models/zhipuai_model.py +38 -3
- camel/retrievers/__init__.py +3 -0
- camel/retrievers/hybrid_retrival.py +237 -0
- camel/toolkits/__init__.py +9 -2
- camel/toolkits/arxiv_toolkit.py +2 -1
- camel/toolkits/ask_news_toolkit.py +4 -2
- camel/toolkits/base.py +22 -3
- camel/toolkits/code_execution.py +2 -0
- camel/toolkits/dappier_toolkit.py +2 -1
- camel/toolkits/data_commons_toolkit.py +38 -12
- camel/toolkits/function_tool.py +13 -0
- camel/toolkits/github_toolkit.py +5 -1
- camel/toolkits/google_maps_toolkit.py +2 -1
- camel/toolkits/google_scholar_toolkit.py +2 -0
- camel/toolkits/human_toolkit.py +0 -3
- camel/toolkits/linkedin_toolkit.py +3 -2
- camel/toolkits/meshy_toolkit.py +3 -2
- camel/toolkits/mineru_toolkit.py +178 -0
- camel/toolkits/networkx_toolkit.py +240 -0
- camel/toolkits/notion_toolkit.py +2 -0
- camel/toolkits/openbb_toolkit.py +3 -2
- camel/toolkits/reddit_toolkit.py +11 -3
- camel/toolkits/retrieval_toolkit.py +6 -1
- camel/toolkits/semantic_scholar_toolkit.py +2 -1
- camel/toolkits/stripe_toolkit.py +8 -2
- camel/toolkits/sympy_toolkit.py +44 -1
- camel/toolkits/video_toolkit.py +2 -0
- camel/toolkits/whatsapp_toolkit.py +3 -2
- camel/toolkits/zapier_toolkit.py +191 -0
- camel/types/__init__.py +2 -2
- camel/types/agents/__init__.py +16 -0
- camel/types/agents/tool_calling_record.py +52 -0
- camel/types/enums.py +3 -0
- camel/types/openai_types.py +16 -14
- camel/utils/__init__.py +2 -1
- camel/utils/async_func.py +2 -2
- camel/utils/commons.py +114 -1
- camel/verifiers/__init__.py +23 -0
- camel/verifiers/base.py +340 -0
- camel/verifiers/models.py +82 -0
- camel/verifiers/python_verifier.py +202 -0
- {camel_ai-0.2.21.dist-info → camel_ai-0.2.23a0.dist-info}/METADATA +273 -256
- {camel_ai-0.2.21.dist-info → camel_ai-0.2.23a0.dist-info}/RECORD +106 -85
- {camel_ai-0.2.21.dist-info → camel_ai-0.2.23a0.dist-info}/WHEEL +1 -1
- {camel_ai-0.2.21.dist-info → camel_ai-0.2.23a0.dist-info}/LICENSE +0 -0
camel/models/qwen_model.py
CHANGED
|
@@ -13,13 +13,15 @@
|
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
14
|
|
|
15
15
|
import os
|
|
16
|
-
from typing import Any, Dict, List, Optional, Union
|
|
16
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
17
17
|
|
|
18
|
-
from openai import OpenAI, Stream
|
|
18
|
+
from openai import AsyncOpenAI, AsyncStream, OpenAI, Stream
|
|
19
|
+
from pydantic import BaseModel
|
|
19
20
|
|
|
20
21
|
from camel.configs import QWEN_API_PARAMS, QwenConfig
|
|
21
22
|
from camel.messages import OpenAIMessage
|
|
22
23
|
from camel.models import BaseModelBackend
|
|
24
|
+
from camel.models._utils import try_modify_message_with_format
|
|
23
25
|
from camel.types import (
|
|
24
26
|
ChatCompletion,
|
|
25
27
|
ChatCompletionChunk,
|
|
@@ -81,10 +83,46 @@ class QwenModel(BaseModelBackend):
|
|
|
81
83
|
api_key=self._api_key,
|
|
82
84
|
base_url=self._url,
|
|
83
85
|
)
|
|
86
|
+
self._async_client = AsyncOpenAI(
|
|
87
|
+
timeout=180,
|
|
88
|
+
max_retries=3,
|
|
89
|
+
api_key=self._api_key,
|
|
90
|
+
base_url=self._url,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
async def _arun(
|
|
94
|
+
self,
|
|
95
|
+
messages: List[OpenAIMessage],
|
|
96
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
97
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
98
|
+
) -> Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
|
|
99
|
+
r"""Runs inference of Qwen chat completion.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
messages (List[OpenAIMessage]): Message list with the chat history
|
|
103
|
+
in OpenAI API format.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
|
|
107
|
+
`ChatCompletion` in the non-stream mode, or
|
|
108
|
+
`AsyncStream[ChatCompletionChunk]` in the stream mode.
|
|
109
|
+
"""
|
|
110
|
+
request_config = self._prepare_request(
|
|
111
|
+
messages, response_format, tools
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
response = await self._async_client.chat.completions.create(
|
|
115
|
+
messages=messages,
|
|
116
|
+
model=self.model_type,
|
|
117
|
+
**request_config,
|
|
118
|
+
)
|
|
119
|
+
return response
|
|
84
120
|
|
|
85
|
-
def
|
|
121
|
+
def _run(
|
|
86
122
|
self,
|
|
87
123
|
messages: List[OpenAIMessage],
|
|
124
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
125
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
88
126
|
) -> Union[ChatCompletion, Stream[ChatCompletionChunk]]:
|
|
89
127
|
r"""Runs inference of Qwen chat completion.
|
|
90
128
|
|
|
@@ -97,13 +135,32 @@ class QwenModel(BaseModelBackend):
|
|
|
97
135
|
`ChatCompletion` in the non-stream mode, or
|
|
98
136
|
`Stream[ChatCompletionChunk]` in the stream mode.
|
|
99
137
|
"""
|
|
138
|
+
request_config = self._prepare_request(
|
|
139
|
+
messages, response_format, tools
|
|
140
|
+
)
|
|
141
|
+
|
|
100
142
|
response = self._client.chat.completions.create(
|
|
101
143
|
messages=messages,
|
|
102
144
|
model=self.model_type,
|
|
103
|
-
**
|
|
145
|
+
**request_config,
|
|
104
146
|
)
|
|
105
147
|
return response
|
|
106
148
|
|
|
149
|
+
def _prepare_request(
|
|
150
|
+
self,
|
|
151
|
+
messages: List[OpenAIMessage],
|
|
152
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
153
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
154
|
+
) -> Dict[str, Any]:
|
|
155
|
+
request_config = self.model_config_dict.copy()
|
|
156
|
+
if tools:
|
|
157
|
+
request_config["tools"] = tools
|
|
158
|
+
elif response_format:
|
|
159
|
+
try_modify_message_with_format(messages[-1], response_format)
|
|
160
|
+
request_config["response_format"] = {"type": "json_object"}
|
|
161
|
+
|
|
162
|
+
return request_config
|
|
163
|
+
|
|
107
164
|
@property
|
|
108
165
|
def token_counter(self) -> BaseTokenCounter:
|
|
109
166
|
r"""Initialize the token counter for the model backend.
|
camel/models/reka_model.py
CHANGED
|
@@ -11,7 +11,9 @@
|
|
|
11
11
|
# See the License for the specific language governing permissions and
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel
|
|
15
17
|
|
|
16
18
|
from camel.configs import REKA_API_PARAMS, RekaConfig
|
|
17
19
|
from camel.messages import OpenAIMessage
|
|
@@ -70,7 +72,7 @@ class RekaModel(BaseModelBackend):
|
|
|
70
72
|
url: Optional[str] = None,
|
|
71
73
|
token_counter: Optional[BaseTokenCounter] = None,
|
|
72
74
|
) -> None:
|
|
73
|
-
from reka.client import Reka
|
|
75
|
+
from reka.client import AsyncReka, Reka
|
|
74
76
|
|
|
75
77
|
if model_config_dict is None:
|
|
76
78
|
model_config_dict = RekaConfig().as_dict()
|
|
@@ -80,6 +82,9 @@ class RekaModel(BaseModelBackend):
|
|
|
80
82
|
model_type, model_config_dict, api_key, url, token_counter
|
|
81
83
|
)
|
|
82
84
|
self._client = Reka(api_key=self._api_key, base_url=self._url)
|
|
85
|
+
self._async_client = AsyncReka(
|
|
86
|
+
api_key=self._api_key, base_url=self._url
|
|
87
|
+
)
|
|
83
88
|
|
|
84
89
|
def _convert_reka_to_openai_response(
|
|
85
90
|
self, response: 'ChatResponse'
|
|
@@ -117,6 +122,8 @@ class RekaModel(BaseModelBackend):
|
|
|
117
122
|
def _convert_openai_to_reka_messages(
|
|
118
123
|
self,
|
|
119
124
|
messages: List[OpenAIMessage],
|
|
125
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
126
|
+
tools: Optional[List[str]] = None,
|
|
120
127
|
) -> List["ChatMessage"]:
|
|
121
128
|
r"""Converts OpenAI API messages to Reka API messages.
|
|
122
129
|
|
|
@@ -173,9 +180,52 @@ class RekaModel(BaseModelBackend):
|
|
|
173
180
|
)
|
|
174
181
|
return self._token_counter
|
|
175
182
|
|
|
176
|
-
def
|
|
183
|
+
async def _arun(
|
|
184
|
+
self,
|
|
185
|
+
messages: List[OpenAIMessage],
|
|
186
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
187
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
188
|
+
) -> ChatCompletion:
|
|
189
|
+
r"""Runs inference of Mistral chat completion.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
messages (List[OpenAIMessage]): Message list with the chat history
|
|
193
|
+
in OpenAI API format.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
ChatCompletion.
|
|
197
|
+
"""
|
|
198
|
+
reka_messages = self._convert_openai_to_reka_messages(messages)
|
|
199
|
+
|
|
200
|
+
response = await self._async_client.chat.create(
|
|
201
|
+
messages=reka_messages,
|
|
202
|
+
model=self.model_type,
|
|
203
|
+
**self.model_config_dict,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
openai_response = self._convert_reka_to_openai_response(response)
|
|
207
|
+
|
|
208
|
+
# Add AgentOps LLM Event tracking
|
|
209
|
+
if LLMEvent:
|
|
210
|
+
llm_event = LLMEvent(
|
|
211
|
+
thread_id=openai_response.id,
|
|
212
|
+
prompt=" ".join(
|
|
213
|
+
[message.get("content") for message in messages] # type: ignore[misc]
|
|
214
|
+
),
|
|
215
|
+
prompt_tokens=openai_response.usage.input_tokens, # type: ignore[union-attr]
|
|
216
|
+
completion=openai_response.choices[0].message.content,
|
|
217
|
+
completion_tokens=openai_response.usage.output_tokens, # type: ignore[union-attr]
|
|
218
|
+
model=self.model_type,
|
|
219
|
+
)
|
|
220
|
+
record(llm_event)
|
|
221
|
+
|
|
222
|
+
return openai_response
|
|
223
|
+
|
|
224
|
+
def _run(
|
|
177
225
|
self,
|
|
178
226
|
messages: List[OpenAIMessage],
|
|
227
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
228
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
179
229
|
) -> ChatCompletion:
|
|
180
230
|
r"""Runs inference of Mistral chat completion.
|
|
181
231
|
|
camel/models/samba_model.py
CHANGED
|
@@ -15,10 +15,11 @@ import json
|
|
|
15
15
|
import os
|
|
16
16
|
import time
|
|
17
17
|
import uuid
|
|
18
|
-
from typing import Any, Dict, List, Optional, Union
|
|
18
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
19
19
|
|
|
20
20
|
import httpx
|
|
21
|
-
from openai import OpenAI, Stream
|
|
21
|
+
from openai import AsyncOpenAI, AsyncStream, OpenAI, Stream
|
|
22
|
+
from pydantic import BaseModel
|
|
22
23
|
|
|
23
24
|
from camel.configs import (
|
|
24
25
|
SAMBA_CLOUD_API_PARAMS,
|
|
@@ -105,6 +106,12 @@ class SambaModel(BaseModelBackend):
|
|
|
105
106
|
base_url=self._url,
|
|
106
107
|
api_key=self._api_key,
|
|
107
108
|
)
|
|
109
|
+
self._async_client = AsyncOpenAI(
|
|
110
|
+
timeout=180,
|
|
111
|
+
max_retries=3,
|
|
112
|
+
base_url=self._url,
|
|
113
|
+
api_key=self._api_key,
|
|
114
|
+
)
|
|
108
115
|
|
|
109
116
|
@property
|
|
110
117
|
def token_counter(self) -> BaseTokenCounter:
|
|
@@ -148,8 +155,35 @@ class SambaModel(BaseModelBackend):
|
|
|
148
155
|
" SambaNova service"
|
|
149
156
|
)
|
|
150
157
|
|
|
151
|
-
def
|
|
152
|
-
self,
|
|
158
|
+
async def _arun( # type: ignore[misc]
|
|
159
|
+
self,
|
|
160
|
+
messages: List[OpenAIMessage],
|
|
161
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
162
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
163
|
+
) -> Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
|
|
164
|
+
r"""Runs SambaNova's service.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
messages (List[OpenAIMessage]): Message list with the chat history
|
|
168
|
+
in OpenAI API format.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
|
|
172
|
+
`ChatCompletion` in the non-stream mode, or
|
|
173
|
+
`AsyncStream[ChatCompletionChunk]` in the stream mode.
|
|
174
|
+
"""
|
|
175
|
+
if "tools" in self.model_config_dict:
|
|
176
|
+
del self.model_config_dict["tools"]
|
|
177
|
+
if self.model_config_dict.get("stream") is True:
|
|
178
|
+
return await self._arun_streaming(messages)
|
|
179
|
+
else:
|
|
180
|
+
return await self._arun_non_streaming(messages)
|
|
181
|
+
|
|
182
|
+
def _run( # type: ignore[misc]
|
|
183
|
+
self,
|
|
184
|
+
messages: List[OpenAIMessage],
|
|
185
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
186
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
153
187
|
) -> Union[ChatCompletion, Stream[ChatCompletionChunk]]:
|
|
154
188
|
r"""Runs SambaNova's service.
|
|
155
189
|
|
|
@@ -398,3 +432,174 @@ class SambaModel(BaseModelBackend):
|
|
|
398
432
|
bool: Whether the model is in stream mode.
|
|
399
433
|
"""
|
|
400
434
|
return self.model_config_dict.get('stream', False)
|
|
435
|
+
|
|
436
|
+
async def _arun_streaming(
|
|
437
|
+
self, messages: List[OpenAIMessage]
|
|
438
|
+
) -> AsyncStream[ChatCompletionChunk]:
|
|
439
|
+
r"""Handles streaming inference with SambaNova's API.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
messages (List[OpenAIMessage]): A list of messages representing the
|
|
443
|
+
chat history in OpenAI API format.
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
AsyncStream[ChatCompletionChunk]: A generator yielding
|
|
447
|
+
`ChatCompletionChunk` objects as they are received from the
|
|
448
|
+
API.
|
|
449
|
+
|
|
450
|
+
Raises:
|
|
451
|
+
RuntimeError: If the HTTP request fails.
|
|
452
|
+
ValueError: If the API doesn't support stream mode.
|
|
453
|
+
"""
|
|
454
|
+
# Handle SambaNova's Cloud API
|
|
455
|
+
if self._url == "https://api.sambanova.ai/v1":
|
|
456
|
+
response = await self._async_client.chat.completions.create(
|
|
457
|
+
messages=messages,
|
|
458
|
+
model=self.model_type,
|
|
459
|
+
**self.model_config_dict,
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# Add AgentOps LLM Event tracking
|
|
463
|
+
if LLMEvent:
|
|
464
|
+
llm_event = LLMEvent(
|
|
465
|
+
thread_id=response.id,
|
|
466
|
+
prompt=" ".join(
|
|
467
|
+
[message.get("content") for message in messages] # type: ignore[misc]
|
|
468
|
+
),
|
|
469
|
+
prompt_tokens=response.usage.prompt_tokens, # type: ignore[union-attr]
|
|
470
|
+
completion=response.choices[0].message.content,
|
|
471
|
+
completion_tokens=response.usage.completion_tokens, # type: ignore[union-attr]
|
|
472
|
+
model=self.model_type,
|
|
473
|
+
)
|
|
474
|
+
record(llm_event)
|
|
475
|
+
|
|
476
|
+
return response
|
|
477
|
+
|
|
478
|
+
elif self._url == "https://sambaverse.sambanova.ai/api/predict":
|
|
479
|
+
raise ValueError(
|
|
480
|
+
"https://sambaverse.sambanova.ai/api/predict doesn't support"
|
|
481
|
+
" stream mode"
|
|
482
|
+
)
|
|
483
|
+
raise RuntimeError(f"Unknown URL: {self._url}")
|
|
484
|
+
|
|
485
|
+
async def _arun_non_streaming(
|
|
486
|
+
self, messages: List[OpenAIMessage]
|
|
487
|
+
) -> ChatCompletion:
|
|
488
|
+
r"""Handles non-streaming inference with SambaNova's API.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
messages (List[OpenAIMessage]): A list of messages representing the
|
|
492
|
+
message in OpenAI API format.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
ChatCompletion: A `ChatCompletion` object containing the complete
|
|
496
|
+
response from the API.
|
|
497
|
+
|
|
498
|
+
Raises:
|
|
499
|
+
RuntimeError: If the HTTP request fails.
|
|
500
|
+
ValueError: If the JSON response cannot be decoded or is missing
|
|
501
|
+
expected data.
|
|
502
|
+
"""
|
|
503
|
+
# Handle SambaNova's Cloud API
|
|
504
|
+
if self._url == "https://api.sambanova.ai/v1":
|
|
505
|
+
response = await self._async_client.chat.completions.create(
|
|
506
|
+
messages=messages,
|
|
507
|
+
model=self.model_type,
|
|
508
|
+
**self.model_config_dict,
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
# Add AgentOps LLM Event tracking
|
|
512
|
+
if LLMEvent:
|
|
513
|
+
llm_event = LLMEvent(
|
|
514
|
+
thread_id=response.id,
|
|
515
|
+
prompt=" ".join(
|
|
516
|
+
[message.get("content") for message in messages] # type: ignore[misc]
|
|
517
|
+
),
|
|
518
|
+
prompt_tokens=response.usage.prompt_tokens, # type: ignore[union-attr]
|
|
519
|
+
completion=response.choices[0].message.content,
|
|
520
|
+
completion_tokens=response.usage.completion_tokens, # type: ignore[union-attr]
|
|
521
|
+
model=self.model_type,
|
|
522
|
+
)
|
|
523
|
+
record(llm_event)
|
|
524
|
+
|
|
525
|
+
return response
|
|
526
|
+
|
|
527
|
+
# Handle SambaNova's Sambaverse API
|
|
528
|
+
else:
|
|
529
|
+
headers = {
|
|
530
|
+
"Content-Type": "application/json",
|
|
531
|
+
"key": str(self._api_key),
|
|
532
|
+
"modelName": self.model_type,
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
data = {
|
|
536
|
+
"instance": json.dumps(
|
|
537
|
+
{
|
|
538
|
+
"conversation_id": str(uuid.uuid4()),
|
|
539
|
+
"messages": messages,
|
|
540
|
+
}
|
|
541
|
+
),
|
|
542
|
+
"params": {
|
|
543
|
+
"do_sample": {"type": "bool", "value": "true"},
|
|
544
|
+
"max_tokens_to_generate": {
|
|
545
|
+
"type": "int",
|
|
546
|
+
"value": str(self.model_config_dict.get("max_tokens")),
|
|
547
|
+
},
|
|
548
|
+
"process_prompt": {"type": "bool", "value": "true"},
|
|
549
|
+
"repetition_penalty": {
|
|
550
|
+
"type": "float",
|
|
551
|
+
"value": str(
|
|
552
|
+
self.model_config_dict.get("repetition_penalty")
|
|
553
|
+
),
|
|
554
|
+
},
|
|
555
|
+
"return_token_count_only": {
|
|
556
|
+
"type": "bool",
|
|
557
|
+
"value": "false",
|
|
558
|
+
},
|
|
559
|
+
"select_expert": {
|
|
560
|
+
"type": "str",
|
|
561
|
+
"value": self.model_type.split("/")[1],
|
|
562
|
+
},
|
|
563
|
+
"stop_sequences": {
|
|
564
|
+
"type": "str",
|
|
565
|
+
"value": self.model_config_dict.get("stop_sequences"),
|
|
566
|
+
},
|
|
567
|
+
"temperature": {
|
|
568
|
+
"type": "float",
|
|
569
|
+
"value": str(
|
|
570
|
+
self.model_config_dict.get("temperature")
|
|
571
|
+
),
|
|
572
|
+
},
|
|
573
|
+
"top_k": {
|
|
574
|
+
"type": "int",
|
|
575
|
+
"value": str(self.model_config_dict.get("top_k")),
|
|
576
|
+
},
|
|
577
|
+
"top_p": {
|
|
578
|
+
"type": "float",
|
|
579
|
+
"value": str(self.model_config_dict.get("top_p")),
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
try:
|
|
585
|
+
# Send the request and handle the response
|
|
586
|
+
with httpx.Client() as client:
|
|
587
|
+
response = client.post(
|
|
588
|
+
self._url, # type: ignore[arg-type]
|
|
589
|
+
headers=headers,
|
|
590
|
+
json=data,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
raw_text = response.text
|
|
594
|
+
# Split the string into two dictionaries
|
|
595
|
+
dicts = raw_text.split("}\n{")
|
|
596
|
+
|
|
597
|
+
# Keep only the last dictionary
|
|
598
|
+
last_dict = "{" + dicts[-1]
|
|
599
|
+
|
|
600
|
+
# Parse the dictionary
|
|
601
|
+
last_dict = json.loads(last_dict)
|
|
602
|
+
return self._sambaverse_to_openai_response(last_dict) # type: ignore[arg-type]
|
|
603
|
+
|
|
604
|
+
except httpx.HTTPStatusError:
|
|
605
|
+
raise RuntimeError(f"HTTP request failed: {raw_text}")
|
camel/models/sglang_model.py
CHANGED
|
@@ -12,11 +12,13 @@
|
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
14
|
import logging
|
|
15
|
+
import subprocess
|
|
15
16
|
import threading
|
|
16
17
|
import time
|
|
17
|
-
from typing import Any, Dict, List, Optional, Union
|
|
18
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
18
19
|
|
|
19
|
-
from openai import OpenAI, Stream
|
|
20
|
+
from openai import AsyncOpenAI, AsyncStream, OpenAI, Stream
|
|
21
|
+
from pydantic import BaseModel
|
|
20
22
|
|
|
21
23
|
from camel.configs import SGLANG_API_PARAMS, SGLangConfig
|
|
22
24
|
from camel.messages import OpenAIMessage
|
|
@@ -85,13 +87,14 @@ class SGLangModel(BaseModelBackend):
|
|
|
85
87
|
api_key="Set-but-ignored", # required but ignored
|
|
86
88
|
base_url=self._url,
|
|
87
89
|
)
|
|
90
|
+
self._async_client = AsyncOpenAI(
|
|
91
|
+
timeout=180,
|
|
92
|
+
max_retries=3,
|
|
93
|
+
api_key="Set-but-ignored", # required but ignored
|
|
94
|
+
base_url=self._url,
|
|
95
|
+
)
|
|
88
96
|
|
|
89
97
|
def _start_server(self) -> None:
|
|
90
|
-
from sglang.utils import ( # type: ignore[import-untyped]
|
|
91
|
-
execute_shell_command,
|
|
92
|
-
wait_for_server,
|
|
93
|
-
)
|
|
94
|
-
|
|
95
98
|
try:
|
|
96
99
|
if not self._url:
|
|
97
100
|
cmd = (
|
|
@@ -101,10 +104,10 @@ class SGLangModel(BaseModelBackend):
|
|
|
101
104
|
f"--host 0.0.0.0"
|
|
102
105
|
)
|
|
103
106
|
|
|
104
|
-
server_process =
|
|
105
|
-
|
|
107
|
+
server_process = _execute_shell_command(cmd)
|
|
108
|
+
_wait_for_server("http://localhost:30000")
|
|
106
109
|
self._url = "http://127.0.0.1:30000/v1"
|
|
107
|
-
self.server_process = server_process
|
|
110
|
+
self.server_process = server_process # type: ignore[assignment]
|
|
108
111
|
# Start the inactivity monitor in a background thread
|
|
109
112
|
self._inactivity_thread = threading.Thread(
|
|
110
113
|
target=self._monitor_inactivity, daemon=True
|
|
@@ -131,8 +134,6 @@ class SGLangModel(BaseModelBackend):
|
|
|
131
134
|
r"""Monitor whether the server process has been inactive for over 10
|
|
132
135
|
minutes.
|
|
133
136
|
"""
|
|
134
|
-
from sglang.utils import terminate_process
|
|
135
|
-
|
|
136
137
|
while True:
|
|
137
138
|
# Check every 10 seconds
|
|
138
139
|
time.sleep(10)
|
|
@@ -143,7 +144,7 @@ class SGLangModel(BaseModelBackend):
|
|
|
143
144
|
time.time() - self.last_run_time > 600
|
|
144
145
|
):
|
|
145
146
|
if self.server_process:
|
|
146
|
-
|
|
147
|
+
_terminate_process(self.server_process)
|
|
147
148
|
self.server_process = None
|
|
148
149
|
self._client = None # Invalidate the client
|
|
149
150
|
logging.info(
|
|
@@ -178,9 +179,49 @@ class SGLangModel(BaseModelBackend):
|
|
|
178
179
|
"input into SGLang model backend."
|
|
179
180
|
)
|
|
180
181
|
|
|
181
|
-
def
|
|
182
|
+
async def _arun(
|
|
182
183
|
self,
|
|
183
184
|
messages: List[OpenAIMessage],
|
|
185
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
186
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
187
|
+
) -> Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
|
|
188
|
+
r"""Runs inference of OpenAI chat completion.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
messages (List[OpenAIMessage]): Message list with the chat history
|
|
192
|
+
in OpenAI API format.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
|
|
196
|
+
`ChatCompletion` in the non-stream mode, or
|
|
197
|
+
`AsyncStream[ChatCompletionChunk]` in the stream mode.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
# Ensure server is running
|
|
201
|
+
self._ensure_server_running()
|
|
202
|
+
|
|
203
|
+
with self._lock:
|
|
204
|
+
# Update last run time
|
|
205
|
+
self.last_run_time = time.time()
|
|
206
|
+
|
|
207
|
+
if self._client is None:
|
|
208
|
+
raise RuntimeError(
|
|
209
|
+
"Client is not initialized. Ensure the server is running."
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
response = await self._async_client.chat.completions.create(
|
|
213
|
+
messages=messages,
|
|
214
|
+
model=self.model_type,
|
|
215
|
+
**self.model_config_dict,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return response
|
|
219
|
+
|
|
220
|
+
def _run(
|
|
221
|
+
self,
|
|
222
|
+
messages: List[OpenAIMessage],
|
|
223
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
224
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
184
225
|
) -> Union[ChatCompletion, Stream[ChatCompletionChunk]]:
|
|
185
226
|
r"""Runs inference of OpenAI chat completion.
|
|
186
227
|
|
|
@@ -223,3 +264,101 @@ class SGLangModel(BaseModelBackend):
|
|
|
223
264
|
bool: Whether the model is in stream mode.
|
|
224
265
|
"""
|
|
225
266
|
return self.model_config_dict.get('stream', False)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# Below are helper functions from sglang.utils
|
|
270
|
+
def _terminate_process(process):
|
|
271
|
+
_kill_process_tree(process.pid)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _kill_process_tree(
|
|
275
|
+
parent_pid, include_parent: bool = True, skip_pid: Optional[int] = None
|
|
276
|
+
):
|
|
277
|
+
r"""Kill the process and all its child processes."""
|
|
278
|
+
import os
|
|
279
|
+
import signal
|
|
280
|
+
|
|
281
|
+
import psutil
|
|
282
|
+
|
|
283
|
+
if parent_pid is None:
|
|
284
|
+
parent_pid = os.getpid()
|
|
285
|
+
include_parent = False
|
|
286
|
+
|
|
287
|
+
try:
|
|
288
|
+
itself = psutil.Process(parent_pid)
|
|
289
|
+
except psutil.NoSuchProcess:
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
children = itself.children(recursive=True)
|
|
293
|
+
for child in children:
|
|
294
|
+
if child.pid == skip_pid:
|
|
295
|
+
continue
|
|
296
|
+
try:
|
|
297
|
+
child.kill()
|
|
298
|
+
except psutil.NoSuchProcess:
|
|
299
|
+
pass
|
|
300
|
+
|
|
301
|
+
if include_parent:
|
|
302
|
+
try:
|
|
303
|
+
itself.kill()
|
|
304
|
+
|
|
305
|
+
# Sometime processes cannot be killed with SIGKILL
|
|
306
|
+
# so we send an additional signal to kill them.
|
|
307
|
+
itself.send_signal(signal.SIGQUIT)
|
|
308
|
+
except psutil.NoSuchProcess:
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _execute_shell_command(command: str) -> subprocess.Popen:
|
|
313
|
+
r"""Execute a shell command and return the process handle
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
command: Shell command as a string (can include \\ line continuations)
|
|
317
|
+
Returns:
|
|
318
|
+
subprocess.Popen: Process handle
|
|
319
|
+
"""
|
|
320
|
+
import subprocess
|
|
321
|
+
|
|
322
|
+
# Replace \ newline with space and split
|
|
323
|
+
command = command.replace("\\\n", " ").replace("\\", " ")
|
|
324
|
+
parts = command.split()
|
|
325
|
+
|
|
326
|
+
return subprocess.Popen(parts, text=True, stderr=subprocess.STDOUT)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _wait_for_server(base_url: str, timeout: Optional[int] = None) -> None:
|
|
330
|
+
r"""Wait for the server to be ready by polling the /v1/models endpoint.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
base_url: The base URL of the server
|
|
334
|
+
timeout: Maximum time to wait in seconds. None means wait forever.
|
|
335
|
+
"""
|
|
336
|
+
import requests
|
|
337
|
+
|
|
338
|
+
start_time = time.time()
|
|
339
|
+
while True:
|
|
340
|
+
try:
|
|
341
|
+
response = requests.get(
|
|
342
|
+
f"{base_url}/v1/models",
|
|
343
|
+
headers={"Authorization": "Bearer None"},
|
|
344
|
+
)
|
|
345
|
+
if response.status_code == 200:
|
|
346
|
+
time.sleep(5)
|
|
347
|
+
print(
|
|
348
|
+
"""\n
|
|
349
|
+
NOTE: Typically, the server runs in a separate terminal.
|
|
350
|
+
In this notebook, we run the server and notebook code
|
|
351
|
+
together, so their outputs are combined.
|
|
352
|
+
To improve clarity, the server logs are displayed in the
|
|
353
|
+
original black color, while the notebook outputs are
|
|
354
|
+
highlighted in blue.
|
|
355
|
+
"""
|
|
356
|
+
)
|
|
357
|
+
break
|
|
358
|
+
|
|
359
|
+
if timeout and time.time() - start_time > timeout:
|
|
360
|
+
raise TimeoutError(
|
|
361
|
+
"Server did not become ready within timeout period"
|
|
362
|
+
)
|
|
363
|
+
except requests.exceptions.RequestException:
|
|
364
|
+
time.sleep(1)
|
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
# limitations under the License.
|
|
13
13
|
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
14
|
import os
|
|
15
|
-
from typing import Any, Dict, List, Optional, Union
|
|
15
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
16
16
|
|
|
17
|
-
from openai import OpenAI, Stream
|
|
17
|
+
from openai import AsyncStream, OpenAI, Stream
|
|
18
|
+
from pydantic import BaseModel
|
|
18
19
|
|
|
19
20
|
from camel.configs import SILICONFLOW_API_PARAMS, SiliconFlowConfig
|
|
20
21
|
from camel.messages import OpenAIMessage
|
|
@@ -82,9 +83,11 @@ class SiliconFlowModel(BaseModelBackend):
|
|
|
82
83
|
base_url=self._url,
|
|
83
84
|
)
|
|
84
85
|
|
|
85
|
-
def
|
|
86
|
+
def _run(
|
|
86
87
|
self,
|
|
87
88
|
messages: List[OpenAIMessage],
|
|
89
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
90
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
88
91
|
) -> Union[ChatCompletion, Stream[ChatCompletionChunk]]:
|
|
89
92
|
r"""Runs inference of SiliconFlow chat completion.
|
|
90
93
|
|
|
@@ -104,6 +107,16 @@ class SiliconFlowModel(BaseModelBackend):
|
|
|
104
107
|
)
|
|
105
108
|
return response
|
|
106
109
|
|
|
110
|
+
async def _arun(
|
|
111
|
+
self,
|
|
112
|
+
messages: List[OpenAIMessage],
|
|
113
|
+
response_format: Optional[Type[BaseModel]] = None,
|
|
114
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
115
|
+
) -> Union[ChatCompletion, AsyncStream[ChatCompletionChunk]]:
|
|
116
|
+
raise NotImplementedError(
|
|
117
|
+
"SiliconFlow does not support async inference."
|
|
118
|
+
)
|
|
119
|
+
|
|
107
120
|
@property
|
|
108
121
|
def token_counter(self) -> BaseTokenCounter:
|
|
109
122
|
r"""Initialize the token counter for the model backend.
|