Skip to content

Commit aa054f9

Browse files
ocss884lightaime
andauthored
Resolve pydantic v1 conflict (camel-ai#434)
Co-authored-by: lig <[email protected]>
1 parent 69b743b commit aa054f9

File tree

7 files changed

+211
-151
lines changed

7 files changed

+211
-151
lines changed

camel/functions/openai_function.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@
1818
from jsonschema.exceptions import SchemaError
1919
from jsonschema.validators import Draft202012Validator as JSONValidator
2020
from pydantic import ValidationError, create_model
21-
from pydantic.alias_generators import to_pascal
2221
from pydantic.fields import FieldInfo
2322

23+
from camel.utils import PYDANTIC_V2, to_pascal
24+
2425

2526
def _remove_a_key(d: Dict, remove_key: Any) -> None:
2627
r"""Remove a key from a dictionary recursively."""
@@ -107,8 +108,13 @@ def _create_mol(name, field):
107108
return create_model(name, **field)
108109

109110
model = _create_mol(to_pascal(func.__name__), fields)
110-
parameters_dict = model.model_json_schema()
111-
# The "title" is generated by model.model_json_schema()
111+
# NOTE: Method `.schema()` is deprecated in pydantic v2.
112+
# the result would be identical to `.model_json_schema()` in v2
113+
if PYDANTIC_V2:
114+
parameters_dict = model.model_json_schema() # type: ignore
115+
else:
116+
parameters_dict = model.schema() # type: ignore
117+
# The `"title"` is generated by `model.model_json_schema()`
112118
# but is useless for openai json schema
113119
_remove_a_key(parameters_dict, "title")
114120

@@ -190,10 +196,23 @@ def validate_openai_tool_schema(
190196
# Automatically validates whether the openai_tool_schema passed
191197
# complies with the specifications of the ToolAssistantToolsFunction.
192198
from openai.types.beta.threads.run import ToolAssistantToolsFunction
193-
try:
194-
ToolAssistantToolsFunction.model_validate(openai_tool_schema)
195-
except ValidationError as e:
196-
raise e
199+
200+
# NOTE: Pydantic v1 does not have a strict mode to check input types
201+
# Below is a compromise solution.
202+
# refs: https://docs.pydantic.dev/1.10/blog/pydantic-v2/#strict-mode
203+
if PYDANTIC_V2:
204+
try:
205+
ToolAssistantToolsFunction.model_validate( # type: ignore
206+
openai_tool_schema)
207+
except ValidationError as e:
208+
raise e
209+
elif ToolAssistantToolsFunction(
210+
**openai_tool_schema) != openai_tool_schema:
211+
raise ValidationError( # type: ignore
212+
errors="Validation error for `ToolAssistantToolsFunction`."
213+
"Please check the input data types.",
214+
model=ToolAssistantToolsFunction,
215+
)
197216
# Check the function description
198217
if not openai_tool_schema["function"]["description"]:
199218
raise ValueError("miss function description")

camel/utils/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
get_task_list,
2121
check_server_running,
2222
get_system_information,
23+
to_pascal,
24+
PYDANTIC_V2,
2325
)
2426
from .token_counting import (
2527
get_model_encoding,
@@ -37,6 +39,8 @@
3739
'get_task_list',
3840
'check_server_running',
3941
'get_system_information',
42+
'to_pascal',
43+
'PYDANTIC_V2',
4044
'get_model_encoding',
4145
'BaseTokenCounter',
4246
'OpenAITokenCounter',

camel/utils/commons.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from typing import Any, Callable, List, Optional, Set, TypeVar, cast
2222
from urllib.parse import urlparse
2323

24+
import pydantic
2425
import requests
2526

2627
from camel.types import TaskType
@@ -198,3 +199,30 @@ def get_system_information():
198199
}
199200

200201
return sys_info
202+
203+
204+
def to_pascal(snake: str) -> str:
205+
"""Convert a snake_case string to PascalCase.
206+
207+
Args:
208+
snake (str): The snake_case string to be converted.
209+
210+
Returns:
211+
str: The converted PascalCase string.
212+
"""
213+
# Check if the string is already in PascalCase
214+
if re.match(r'^[A-Z][a-zA-Z0-9]*([A-Z][a-zA-Z0-9]*)*$', snake):
215+
return snake
216+
# Remove leading and trailing underscores
217+
snake = snake.strip('_')
218+
# Replace multiple underscores with a single one
219+
snake = re.sub('_+', '_', snake)
220+
# Convert to PascalCase
221+
return re.sub(
222+
'_([0-9A-Za-z])',
223+
lambda m: m.group(1).upper(),
224+
snake.title(),
225+
)
226+
227+
228+
PYDANTIC_V2 = pydantic.VERSION.startswith("2.")

0 commit comments

Comments
 (0)