Skip to content

Commit 715e102

Browse files
committed
refactor(skills): move skills to ee
1 parent 9f3baf6 commit 715e102

File tree

3 files changed

+262
-0
lines changed

3 files changed

+262
-0
lines changed

‎pandasai/ee/LICENSE‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
The PandasAI Enterprise license (the “Enterprise License”)
2+
Copyright (c) 2024 Sinaptik GmbH
3+
4+
With regard to the PandasAI Software:
5+
6+
This software and associated documentation files (the "Software") may only be
7+
used in production, if you (and any entity that you represent) have agreed to,
8+
and are in compliance with, the PandasAI Subscription Terms of Service, available
9+
at https://pandas-ai.com/terms (the “Enterprise Terms”), or other
10+
agreement governing the use of the Software, as agreed by you and PandasAI,
11+
and otherwise have a valid PandasAI Enterprise license for the
12+
correct number of user seats. Subject to the foregoing sentence, you are free to
13+
modify this Software and publish patches to the Software. You agree that PandasAI
14+
and/or its licensors (as applicable) retain all right, title and interest in and
15+
to all such modifications and/or patches, and all such modifications and/or
16+
patches may only be used, copied, modified, displayed, distributed, or otherwise
17+
exploited with a valid PandasAI Enterprise license for the correct
18+
number of user seats. Notwithstanding the foregoing, you may copy and modify
19+
the Software for development and testing purposes, without requiring a
20+
subscription. You agree that PandasAI and/or its licensors (as applicable) retain
21+
all right, title and interest in and to all such modifications. You are not
22+
granted any other rights beyond what is expressly stated herein. Subject to the
23+
foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
24+
and/or sell the Software.
25+
26+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
32+
SOFTWARE.
33+
34+
For all third party components incorporated into the PandasAI Software, those
35+
components are licensed under the original license provided by the owner of the
36+
applicable component.

‎pandasai/ee/skills/__init__.py‎

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import inspect
2+
from typing import Any, Callable, Optional, Union
3+
4+
from pydantic import BaseModel, PrivateAttr
5+
6+
7+
class SkillType(BaseModel):
8+
"""Skill that takes a function usable by pandasai"""
9+
10+
func: Callable[..., Any]
11+
description: Optional[str] = None
12+
name: Optional[str] = None
13+
_signature: Optional[str] = PrivateAttr()
14+
15+
def __init__(
16+
self,
17+
func: Callable[..., Any],
18+
description: Optional[str] = None,
19+
name: Optional[str] = None,
20+
**kwargs: Any,
21+
) -> None:
22+
"""
23+
Initializes the skill.
24+
25+
Args:
26+
func: The function from which to create a skill
27+
description: The description of the skill.
28+
Defaults to the function docstring.
29+
name: The name of the function. Mandatory when `func` is a lambda.
30+
Defaults to the function's name.
31+
**kwargs: additional params
32+
"""
33+
34+
name = name or func.__name__
35+
description = description or func.__doc__
36+
if description is None:
37+
# if description is None then the function doesn't have a docstring
38+
# and the user didn't provide any description
39+
raise ValueError(
40+
f"Function must have a docstring if no description is provided for skill {name}."
41+
)
42+
signature = f"def {name}{inspect.signature(func)}:"
43+
44+
super(SkillType, self).__init__(
45+
func=func, description=description, name=name, **kwargs
46+
)
47+
self._signature = signature
48+
49+
def __call__(self, *args, **kwargs) -> Any:
50+
"""Calls the skill function"""
51+
return self.func(*args, **kwargs)
52+
53+
@classmethod
54+
def from_function(cls, func: Callable, **kwargs: Any) -> "SkillType":
55+
"""
56+
Creates a skill object from a function
57+
58+
Args:
59+
func: The function from which to create a skill
60+
61+
Returns:
62+
the `Skill` object
63+
64+
"""
65+
return cls(func=func, **kwargs)
66+
67+
def stringify(self):
68+
return inspect.getsource(self.func)
69+
70+
def __str__(self):
71+
return (
72+
f'<function>\n{self._signature}\n """{self.description}"""\n</function>'
73+
)
74+
75+
76+
def skill(*args: Union[str, Callable]) -> Callable:
77+
"""Decorator to create a skill out of functions and automatically add it to the global skills manager.
78+
Can be used without arguments. The function must have a docstring.
79+
80+
Args:
81+
*args: The arguments to the skill
82+
83+
Examples:
84+
.. code-block:: python
85+
86+
@skill
87+
def compute_flight_prices(offers: pd.DataFrame) -> List[float]:
88+
\"\"\"Computes the flight prices\"\"\"
89+
return
90+
91+
@skill("custom_name")
92+
def compute_flight_prices(offers: pd.Dataframe) -> List[float]:
93+
\"\"\"Computes the flight prices\"\"\"
94+
return
95+
"""
96+
97+
def _make_skill_with_name(skill_name: str) -> Callable:
98+
def _make_skill(skill_fn: Callable) -> SkillType:
99+
skill_obj = SkillType(
100+
name=skill_name, # func.__name__ if None
101+
# when this decorator is used, the function MUST have a docstring
102+
description=skill_fn.__doc__,
103+
func=skill_fn,
104+
)
105+
106+
# Automatically add the skill to the global skills manager
107+
try:
108+
from pandasai.ee.skills.manager import SkillsManager
109+
110+
SkillsManager.add_skills(skill_obj)
111+
except ImportError:
112+
# If SkillsManager is not available, just return the skill
113+
pass
114+
115+
return skill_obj
116+
117+
return _make_skill
118+
119+
if len(args) == 1 and isinstance(args[0], str):
120+
# Example: @skill("skillName")
121+
return _make_skill_with_name(args[0])
122+
elif len(args) == 1 and callable(args[0]):
123+
# Example: @skill
124+
return _make_skill_with_name(args[0].__name__)(args[0])
125+
elif not args:
126+
# Covers the case in which a function is decorated with "@skill()"
127+
# with the intended behavior of "@skill"
128+
def _func_wrapper(fn: Callable) -> SkillType:
129+
return _make_skill_with_name(fn.__name__)(fn)
130+
131+
return _func_wrapper
132+
else:
133+
raise ValueError(
134+
f"Too many arguments for skill decorator. Received: {len(args)}"
135+
)
136+
137+
138+
__all__ = ["skill", "SkillType"]

‎pandasai/ee/skills/manager.py‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
from typing import List
2+
3+
from pandasai.ee.skills import SkillType
4+
5+
6+
class SkillsManager:
7+
"""
8+
A singleton class to manage the global skills list.
9+
"""
10+
11+
_skills: List[SkillType] = []
12+
13+
@classmethod
14+
def add_skills(cls, *skills: SkillType):
15+
"""
16+
Add skills to the global list of skills. If a skill with the same name
17+
already exists, raise an error.
18+
19+
Args:
20+
*skills: Variable number of skill objects to add.
21+
"""
22+
for skill in skills:
23+
if any(existing_skill.name == skill.name for existing_skill in cls._skills):
24+
raise ValueError(f"Skill with name '{skill.name}' already exists.")
25+
26+
cls._skills.extend(skills)
27+
28+
@classmethod
29+
def skill_exists(cls, name: str):
30+
"""
31+
Check if a skill with the given name exists in the global list of skills.
32+
33+
Args:
34+
name (str): The name of the skill to check.
35+
36+
Returns:
37+
bool: True if a skill with the given name exists, False otherwise.
38+
"""
39+
return any(skill.name == name for skill in cls._skills)
40+
41+
@classmethod
42+
def has_skills(cls):
43+
"""
44+
Check if there are any skills in the global list of skills.
45+
46+
Returns:
47+
bool: True if there are skills, False otherwise.
48+
"""
49+
return len(cls._skills) > 0
50+
51+
@classmethod
52+
def get_skill_by_func_name(cls, name: str):
53+
"""
54+
Get a skill by its name from the global list.
55+
56+
Args:
57+
name (str): The name of the skill to retrieve.
58+
59+
Returns:
60+
Skill or None: The skill with the given name, or None if not found.
61+
"""
62+
return next((skill for skill in cls._skills if skill.name == name), None)
63+
64+
@classmethod
65+
def get_skills(cls) -> List[SkillType]:
66+
"""
67+
Get the global list of skills.
68+
69+
Returns:
70+
List[SkillType]: The list of all skills.
71+
"""
72+
return cls._skills.copy()
73+
74+
@classmethod
75+
def clear_skills(cls):
76+
"""
77+
Clear all skills from the global list.
78+
"""
79+
cls._skills.clear()
80+
81+
@classmethod
82+
def __str__(cls) -> str:
83+
"""
84+
Present all skills
85+
Returns:
86+
str: String representation of all skills
87+
"""
88+
return "\n".join(str(skill) for skill in cls._skills)

0 commit comments

Comments
 (0)