hyperpocket 0.1.10__py3-none-any.whl → 0.2.0__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.
- hyperpocket/__init__.py +4 -4
- hyperpocket/auth/__init__.py +12 -7
- hyperpocket/auth/calendly/oauth2_handler.py +24 -17
- hyperpocket/auth/calendly/oauth2_schema.py +3 -1
- hyperpocket/auth/context.py +2 -1
- hyperpocket/auth/github/oauth2_handler.py +13 -8
- hyperpocket/auth/github/token_handler.py +27 -21
- hyperpocket/auth/google/context.py +1 -3
- hyperpocket/auth/google/oauth2_context.py +1 -1
- hyperpocket/auth/google/oauth2_handler.py +22 -17
- hyperpocket/auth/gumloop/token_context.py +1 -4
- hyperpocket/auth/gumloop/token_handler.py +48 -20
- hyperpocket/auth/gumloop/token_schema.py +2 -1
- hyperpocket/auth/handler.py +21 -6
- hyperpocket/auth/linear/token_context.py +2 -5
- hyperpocket/auth/linear/token_handler.py +45 -21
- hyperpocket/auth/notion/context.py +2 -2
- hyperpocket/auth/notion/token_context.py +2 -4
- hyperpocket/auth/notion/token_handler.py +45 -21
- hyperpocket/auth/notion/token_schema.py +0 -1
- hyperpocket/auth/reddit/oauth2_handler.py +9 -10
- hyperpocket/auth/reddit/oauth2_schema.py +0 -2
- hyperpocket/auth/schema.py +4 -1
- hyperpocket/auth/slack/oauth2_context.py +3 -1
- hyperpocket/auth/slack/oauth2_handler.py +55 -35
- hyperpocket/auth/slack/token_context.py +2 -4
- hyperpocket/auth/slack/token_handler.py +42 -19
- hyperpocket/builtin.py +4 -2
- hyperpocket/cli/__main__.py +4 -2
- hyperpocket/cli/auth.py +59 -28
- hyperpocket/cli/codegen/auth/auth_context_template.py +3 -2
- hyperpocket/cli/codegen/auth/auth_token_context_template.py +3 -2
- hyperpocket/cli/codegen/auth/auth_token_handler_template.py +6 -5
- hyperpocket/cli/codegen/auth/auth_token_schema_template.py +3 -2
- hyperpocket/cli/codegen/auth/server_auth_template.py +3 -2
- hyperpocket/cli/pull.py +5 -5
- hyperpocket/config/__init__.py +3 -8
- hyperpocket/config/auth.py +3 -1
- hyperpocket/config/logger.py +20 -15
- hyperpocket/config/session.py +4 -2
- hyperpocket/config/settings.py +19 -2
- hyperpocket/futures/__init__.py +1 -1
- hyperpocket/futures/futurestore.py +3 -2
- hyperpocket/pocket_auth.py +171 -84
- hyperpocket/pocket_core.py +51 -33
- hyperpocket/pocket_main.py +122 -93
- hyperpocket/prompts.py +2 -2
- hyperpocket/repository/__init__.py +1 -1
- hyperpocket/repository/lock.py +47 -33
- hyperpocket/repository/lockfile.py +2 -2
- hyperpocket/repository/repository.py +1 -1
- hyperpocket/server/__init__.py +1 -1
- hyperpocket/server/auth/github.py +2 -1
- hyperpocket/server/auth/linear.py +1 -3
- hyperpocket/server/auth/notion.py +2 -5
- hyperpocket/server/auth/slack.py +1 -3
- hyperpocket/server/auth/token.py +17 -11
- hyperpocket/server/proxy.py +29 -13
- hyperpocket/server/server.py +75 -31
- hyperpocket/server/tool/dto/script.py +15 -10
- hyperpocket/server/tool/wasm.py +14 -11
- hyperpocket/session/__init__.py +6 -2
- hyperpocket/session/in_memory.py +44 -24
- hyperpocket/session/interface.py +42 -24
- hyperpocket/session/redis.py +48 -31
- hyperpocket/tool/__init__.py +10 -10
- hyperpocket/tool/function/__init__.py +1 -5
- hyperpocket/tool/function/annotation.py +11 -9
- hyperpocket/tool/function/tool.py +37 -27
- hyperpocket/tool/tool.py +59 -36
- hyperpocket/tool/wasm/__init__.py +1 -1
- hyperpocket/tool/wasm/browser.py +15 -10
- hyperpocket/tool/wasm/invoker.py +16 -16
- hyperpocket/tool/wasm/script.py +27 -14
- hyperpocket/tool/wasm/templates/__init__.py +22 -15
- hyperpocket/tool/wasm/templates/node.py +2 -2
- hyperpocket/tool/wasm/templates/python.py +2 -2
- hyperpocket/tool/wasm/tool.py +27 -14
- hyperpocket/tool_like.py +3 -3
- hyperpocket/util/__init__.py +1 -1
- hyperpocket/util/extract_func_param_desc_from_docstring.py +23 -7
- hyperpocket/util/find_all_leaf_class_in_package.py +4 -3
- hyperpocket/util/find_all_subclass_in_package.py +4 -2
- hyperpocket/util/flatten_json_schema.py +10 -6
- hyperpocket/util/function_to_model.py +33 -12
- hyperpocket/util/get_objects_from_subpackage.py +1 -1
- hyperpocket/util/json_schema_to_model.py +14 -5
- {hyperpocket-0.1.10.dist-info → hyperpocket-0.2.0.dist-info}/METADATA +11 -5
- hyperpocket-0.2.0.dist-info/RECORD +137 -0
- hyperpocket-0.1.10.dist-info/RECORD +0 -137
- {hyperpocket-0.1.10.dist-info → hyperpocket-0.2.0.dist-info}/WHEEL +0 -0
- {hyperpocket-0.1.10.dist-info → hyperpocket-0.2.0.dist-info}/entry_points.txt +0 -0
hyperpocket/tool/wasm/script.py
CHANGED
@@ -14,29 +14,38 @@ class ScriptRuntime(enum.Enum):
|
|
14
14
|
Python = "python"
|
15
15
|
Wasm = "wasm"
|
16
16
|
|
17
|
+
|
17
18
|
_RuntimePackageFiles = {
|
18
19
|
ScriptRuntime.Node: ["dist/index.js"],
|
19
20
|
ScriptRuntime.Python: ["main.py", "requirements.txt"],
|
20
21
|
ScriptRuntime.Wasm: ["dist/index.wasm"],
|
21
22
|
}
|
22
23
|
|
24
|
+
|
23
25
|
class ScriptFileNodeContent(BaseModel):
|
24
26
|
contents: str
|
25
27
|
|
28
|
+
|
26
29
|
class ScriptFileNode(BaseModel):
|
27
|
-
directory: Optional[dict[str,
|
30
|
+
directory: Optional[dict[str, "ScriptFileNode"]] = None
|
28
31
|
file: Optional[ScriptFileNodeContent] = None
|
29
|
-
|
32
|
+
|
30
33
|
@classmethod
|
31
|
-
def create_file_tree(cls, path: str, contents: str) -> dict[str,
|
34
|
+
def create_file_tree(cls, path: str, contents: str) -> dict[str, "ScriptFileNode"]:
|
32
35
|
path_split = path.split("/")
|
33
36
|
if len(path_split) == 1:
|
34
|
-
return {
|
35
|
-
|
37
|
+
return {
|
38
|
+
path_split[0]: ScriptFileNode(
|
39
|
+
file=ScriptFileNodeContent(contents=contents)
|
40
|
+
)
|
41
|
+
}
|
42
|
+
node = cls.create_file_tree("/".join(path_split[1:]), contents)
|
36
43
|
return {path_split[0]: ScriptFileNode(directory=node)}
|
37
|
-
|
44
|
+
|
38
45
|
@staticmethod
|
39
|
-
def merge(
|
46
|
+
def merge(
|
47
|
+
a: dict[str, "ScriptFileNode"], b: [str, "ScriptFileNode"]
|
48
|
+
) -> dict[str, "ScriptFileNode"]:
|
40
49
|
for k, v in b.items():
|
41
50
|
if k in a:
|
42
51
|
if a[k].directory and v.directory:
|
@@ -53,19 +62,21 @@ class Script(BaseModel):
|
|
53
62
|
tool_path: str
|
54
63
|
rendered_html: str
|
55
64
|
runtime: ScriptRuntime
|
56
|
-
|
65
|
+
|
57
66
|
def load_file_tree(self) -> dict[str, ScriptFileNode]:
|
58
67
|
relpaths = _RuntimePackageFiles[self.runtime]
|
59
68
|
file_tree = dict()
|
60
69
|
for p in relpaths:
|
61
70
|
filepath = pathlib.Path(self.tool_path) / p
|
62
71
|
with filepath.open("r") as f:
|
63
|
-
contents = f.read().encode(
|
72
|
+
contents = f.read().encode("utf-8")
|
64
73
|
encoded_bytes = base64.b64encode(contents)
|
65
74
|
encoded_str = encoded_bytes.decode()
|
66
|
-
file_tree = ScriptFileNode.merge(
|
75
|
+
file_tree = ScriptFileNode.merge(
|
76
|
+
file_tree, ScriptFileNode.create_file_tree(p, encoded_str)
|
77
|
+
)
|
67
78
|
return file_tree
|
68
|
-
|
79
|
+
|
69
80
|
@property
|
70
81
|
def package_name(self) -> Optional[str]:
|
71
82
|
if self.runtime != ScriptRuntime.Python:
|
@@ -78,7 +89,7 @@ class Script(BaseModel):
|
|
78
89
|
if not name:
|
79
90
|
raise ValueError("Could not find package name")
|
80
91
|
return name.replace("-", "_")
|
81
|
-
|
92
|
+
|
82
93
|
@property
|
83
94
|
def entrypoint(self) -> str:
|
84
95
|
pocket_logger.info(self.tool_path)
|
@@ -99,10 +110,11 @@ class Script(BaseModel):
|
|
99
110
|
if not wheel_path.exists():
|
100
111
|
raise ValueError(f"Wheel file {wheel_path} does not exist")
|
101
112
|
return wheel_name
|
102
|
-
|
113
|
+
|
103
114
|
def dist_file_path(self, file_name: str) -> str:
|
104
115
|
return str(pathlib.Path(self.tool_path) / "dist" / file_name)
|
105
116
|
|
117
|
+
|
106
118
|
class _ScriptStore(object):
|
107
119
|
scripts: dict[str, Script] = {}
|
108
120
|
|
@@ -113,9 +125,10 @@ class _ScriptStore(object):
|
|
113
125
|
if script.id in self.scripts:
|
114
126
|
raise ValueError("Script id already exists")
|
115
127
|
self.scripts[script.id] = script
|
116
|
-
|
128
|
+
|
117
129
|
def get_script(self, script_id: str) -> Script:
|
118
130
|
# ValueError exception is intentional
|
119
131
|
return self.scripts[script_id]
|
120
132
|
|
133
|
+
|
121
134
|
ScriptStore = _ScriptStore()
|
@@ -1,28 +1,35 @@
|
|
1
1
|
import base64
|
2
2
|
import json
|
3
3
|
|
4
|
-
from jinja2 import
|
4
|
+
from jinja2 import DictLoader, Environment
|
5
5
|
|
6
6
|
from hyperpocket.tool.wasm.templates.node import node_template
|
7
7
|
from hyperpocket.tool.wasm.templates.python import python_template
|
8
8
|
|
9
9
|
TemplateEnvironments = Environment(
|
10
|
-
loader=DictLoader(
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
loader=DictLoader(
|
11
|
+
{
|
12
|
+
"python.html": python_template,
|
13
|
+
"node.html": node_template,
|
14
|
+
}
|
15
|
+
),
|
16
|
+
autoescape=False,
|
15
17
|
)
|
16
18
|
|
17
19
|
|
18
|
-
def render(
|
20
|
+
def render(
|
21
|
+
language: str, script_id: str, env: dict[str, str], body: str, **kwargs
|
22
|
+
) -> str:
|
19
23
|
env_json = json.dumps(env)
|
20
|
-
template = TemplateEnvironments.get_template(f
|
21
|
-
body_bytes = body.encode(
|
24
|
+
template = TemplateEnvironments.get_template(f"{language.lower()}.html")
|
25
|
+
body_bytes = body.encode("utf-8")
|
22
26
|
body_b64_bytes = base64.b64encode(body_bytes)
|
23
|
-
body_b64 = body_b64_bytes.decode(
|
24
|
-
return template.render(
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
body_b64 = body_b64_bytes.decode("ascii")
|
28
|
+
return template.render(
|
29
|
+
**{
|
30
|
+
"SCRIPT_ID": script_id,
|
31
|
+
"ENV_JSON": env_json,
|
32
|
+
"BODY_JSON_B64": body_b64,
|
33
|
+
}
|
34
|
+
| kwargs
|
35
|
+
)
|
hyperpocket/tool/wasm/tool.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
import os
|
2
1
|
import json
|
3
2
|
import pathlib
|
4
3
|
from typing import Any, Optional
|
5
4
|
|
6
5
|
import toml
|
6
|
+
|
7
7
|
from hyperpocket.auth import AuthProvider
|
8
|
-
from hyperpocket.config import
|
8
|
+
from hyperpocket.config import pocket_logger
|
9
9
|
from hyperpocket.repository import Lock, Lockfile
|
10
10
|
from hyperpocket.repository.lock import GitLock, LocalLock
|
11
11
|
from hyperpocket.tool import Tool, ToolRequest
|
@@ -26,15 +26,24 @@ class WasmToolRequest(ToolRequest):
|
|
26
26
|
def __str__(self):
|
27
27
|
return f"ToolRequest(lock={self.lock}, rel_path={self.rel_path})"
|
28
28
|
|
29
|
-
|
29
|
+
|
30
|
+
def from_local(
|
31
|
+
path: str, tool_vars: Optional[dict[str, str]] = None
|
32
|
+
) -> WasmToolRequest:
|
30
33
|
if tool_vars is None:
|
31
34
|
tool_vars = dict()
|
32
35
|
return WasmToolRequest(LocalLock(path), "", tool_vars)
|
33
36
|
|
34
|
-
|
37
|
+
|
38
|
+
def from_git(
|
39
|
+
repository: str, ref: str, rel_path: str, tool_vars: Optional[dict[str, str]] = None
|
40
|
+
) -> WasmToolRequest:
|
35
41
|
if not tool_vars:
|
36
42
|
tool_vars = dict()
|
37
|
-
return WasmToolRequest(
|
43
|
+
return WasmToolRequest(
|
44
|
+
GitLock(repository_url=repository, git_ref=ref), rel_path, tool_vars
|
45
|
+
)
|
46
|
+
|
38
47
|
|
39
48
|
class WasmTool(Tool):
|
40
49
|
"""
|
@@ -55,7 +64,9 @@ class WasmTool(Tool):
|
|
55
64
|
return self._invoker
|
56
65
|
|
57
66
|
@classmethod
|
58
|
-
def from_tool_request(
|
67
|
+
def from_tool_request(
|
68
|
+
cls, tool_req: WasmToolRequest, lockfile: Lockfile = None, **kwargs
|
69
|
+
) -> "WasmTool":
|
59
70
|
if not lockfile:
|
60
71
|
raise ValueError("lockfile is required")
|
61
72
|
tool_req.lock = lockfile.get_lock(tool_req.lock.key())
|
@@ -70,20 +81,22 @@ class WasmTool(Tool):
|
|
70
81
|
with schema_path.open("r") as f:
|
71
82
|
json_schema = json.load(f)
|
72
83
|
except Exception as e:
|
73
|
-
pocket_logger.warning(
|
84
|
+
pocket_logger.warning(
|
85
|
+
f"{toolpkg_path} failed to load json schema. error : {e}"
|
86
|
+
)
|
74
87
|
json_schema = None
|
75
88
|
|
76
89
|
default_tool_vars = dict()
|
77
90
|
try:
|
78
91
|
with config_path.open("r") as f:
|
79
92
|
config = toml.load(f)
|
80
|
-
name = config.get(
|
81
|
-
description = config.get(
|
82
|
-
if language := config.get(
|
93
|
+
name = config.get("name")
|
94
|
+
description = config.get("description")
|
95
|
+
if language := config.get("language"):
|
83
96
|
lang = language.lower()
|
84
|
-
if lang ==
|
97
|
+
if lang == "python":
|
85
98
|
runtime = ScriptRuntime.Python
|
86
|
-
elif lang ==
|
99
|
+
elif lang == "node":
|
87
100
|
runtime = ScriptRuntime.Node
|
88
101
|
else:
|
89
102
|
raise ValueError(f"The language `{lang}` is not supported.")
|
@@ -99,7 +112,7 @@ class WasmTool(Tool):
|
|
99
112
|
readme = f.read()
|
100
113
|
else:
|
101
114
|
readme = None
|
102
|
-
|
115
|
+
|
103
116
|
return cls(
|
104
117
|
name=name,
|
105
118
|
description=description,
|
@@ -127,7 +140,7 @@ class WasmTool(Tool):
|
|
127
140
|
auth_handler=auth_handler,
|
128
141
|
scopes=scopes,
|
129
142
|
)
|
130
|
-
|
143
|
+
|
131
144
|
def template_arguments(self) -> dict[str, str]:
|
132
145
|
return {}
|
133
146
|
|
hyperpocket/tool_like.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Callable, Union
|
2
2
|
|
3
|
-
from hyperpocket.tool import
|
3
|
+
from hyperpocket.tool import Tool, ToolRequest
|
4
4
|
|
5
|
-
ToolLike = Union[Tool, str, Callable, ToolRequest]
|
5
|
+
ToolLike = Union[Tool, str, Callable, ToolRequest]
|
hyperpocket/util/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__all__ = [
|
1
|
+
__all__ = ["json_schema_to_model"]
|
@@ -25,13 +25,17 @@ def extract_param_docstring_mapping(func) -> dict[str, str]:
|
|
25
25
|
|
26
26
|
param_mapping = extract_param_desc_by_google_stype_docstring(docstring, func_params)
|
27
27
|
if param_mapping:
|
28
|
-
pocket_logger.debug(
|
28
|
+
pocket_logger.debug(
|
29
|
+
f"success extract docstring of {func.__name__} by google style!"
|
30
|
+
)
|
29
31
|
return param_mapping
|
30
32
|
pocket_logger.debug(f"not found param desc of {func.__name__} by google style..")
|
31
33
|
|
32
34
|
param_mapping = extract_param_desc_by_other_styles(docstring, func_params)
|
33
35
|
if param_mapping:
|
34
|
-
pocket_logger.debug(
|
36
|
+
pocket_logger.debug(
|
37
|
+
f"success extract docstring of {func.__name__} by other style!"
|
38
|
+
)
|
35
39
|
return param_mapping
|
36
40
|
pocket_logger.debug(f"not found param desc of {func.__name__} by other styles..")
|
37
41
|
|
@@ -50,7 +54,9 @@ def extract_param_docstring_mapping(func) -> dict[str, str]:
|
|
50
54
|
param_descriptions.append((cleaned_param_name, description))
|
51
55
|
|
52
56
|
# Ensure no duplicates and match with function parameters
|
53
|
-
param_mapping = {
|
57
|
+
param_mapping = {
|
58
|
+
param: desc for param, desc in param_descriptions if param in func_params
|
59
|
+
}
|
54
60
|
pocket_logger.debug(f"final param_mapping of {func.__name__} : {param_mapping}")
|
55
61
|
|
56
62
|
return param_mapping
|
@@ -74,11 +80,15 @@ def extract_param_desc_by_other_styles(docstring, func_params) -> dict[str, str]
|
|
74
80
|
cleaned_param = clean_bracket_content(param)
|
75
81
|
param_descriptions.append((cleaned_param, desc.strip()))
|
76
82
|
# Ensure no duplicates and match with function parameters
|
77
|
-
param_mapping = {
|
83
|
+
param_mapping = {
|
84
|
+
param: desc for param, desc in param_descriptions if param in func_params
|
85
|
+
}
|
78
86
|
return param_mapping
|
79
87
|
|
80
88
|
|
81
|
-
def extract_param_desc_by_google_stype_docstring(
|
89
|
+
def extract_param_desc_by_google_stype_docstring(
|
90
|
+
docstring, func_params
|
91
|
+
) -> dict[str, str]:
|
82
92
|
# Regex pattern to extract parameter descriptions in Google style
|
83
93
|
param_pattern = r"Args:\n(.*?)(?=\n\S|$)" # Matches the Args: section
|
84
94
|
match = re.search(param_pattern, docstring, re.DOTALL)
|
@@ -90,11 +100,17 @@ def extract_param_desc_by_google_stype_docstring(docstring, func_params) -> dict
|
|
90
100
|
param_descriptions = {}
|
91
101
|
for line in param_lines:
|
92
102
|
# Match parameter line with "name (type): description"
|
93
|
-
param_match = re.match(
|
103
|
+
param_match = re.match(
|
104
|
+
r"^[^a-zA-Z_]*([a-zA-Z_]\w*)\s*[\(\[]\s*(.*?)\s*[\)\]]\s*:\s*(.*)", line
|
105
|
+
)
|
94
106
|
if param_match:
|
95
107
|
param, _, desc = param_match.groups()
|
96
108
|
cleaned_param = clean_bracket_content(param)
|
97
109
|
param_descriptions[cleaned_param] = desc.strip()
|
98
110
|
# Match parameters to descriptions
|
99
|
-
param_mapping = {
|
111
|
+
param_mapping = {
|
112
|
+
param: desc
|
113
|
+
for param, desc in param_descriptions.items()
|
114
|
+
if param in func_params
|
115
|
+
}
|
100
116
|
return param_mapping
|
@@ -1,12 +1,13 @@
|
|
1
|
-
from typing import
|
2
|
-
|
1
|
+
from typing import List, Type, TypeVar
|
3
2
|
|
4
3
|
from hyperpocket.util.find_all_subclass_in_package import find_all_subclass_in_package
|
5
4
|
|
6
5
|
T = TypeVar("T")
|
7
6
|
|
8
7
|
|
9
|
-
def find_all_leaf_class_in_package(
|
8
|
+
def find_all_leaf_class_in_package(
|
9
|
+
package_name: str, interface_type: Type[T]
|
10
|
+
) -> List[T]:
|
10
11
|
parent_class_set = set()
|
11
12
|
subclasses = find_all_subclass_in_package(package_name, interface_type)
|
12
13
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import importlib
|
2
2
|
import inspect
|
3
3
|
import pkgutil
|
4
|
-
from typing import
|
4
|
+
from typing import List, Type, TypeVar
|
5
5
|
|
6
6
|
from hyperpocket.config import pocket_logger
|
7
7
|
|
@@ -12,7 +12,9 @@ def find_all_subclass_in_package(package_name: str, interface_type: Type[T]) ->
|
|
12
12
|
subclasses = set()
|
13
13
|
package = importlib.import_module(package_name)
|
14
14
|
|
15
|
-
for _, module_name, is_pkg in pkgutil.walk_packages(
|
15
|
+
for _, module_name, is_pkg in pkgutil.walk_packages(
|
16
|
+
package.__path__, package.__name__ + "."
|
17
|
+
):
|
16
18
|
try:
|
17
19
|
if "tests" in module_name or is_pkg:
|
18
20
|
continue
|
@@ -6,7 +6,7 @@ def flatten_json_schema(schema: dict):
|
|
6
6
|
Flatten JSON Schema by resolving all $refs using definitions in $defs
|
7
7
|
and convert to a fully nested schema.
|
8
8
|
"""
|
9
|
-
definitions = schema.get(
|
9
|
+
definitions = schema.get("$defs", {})
|
10
10
|
schema_copy = copy.deepcopy(schema)
|
11
11
|
|
12
12
|
# Resolve references within $defs first
|
@@ -15,7 +15,7 @@ def flatten_json_schema(schema: dict):
|
|
15
15
|
resolved_definitions[key] = resolve_refs(value, definitions)
|
16
16
|
|
17
17
|
# Resolve references in the main schema
|
18
|
-
schema_copy.pop(
|
18
|
+
schema_copy.pop("$defs", None) # Remove $defs
|
19
19
|
return resolve_refs(schema_copy, resolved_definitions)
|
20
20
|
|
21
21
|
|
@@ -28,12 +28,16 @@ def resolve_refs(schema, definitions):
|
|
28
28
|
resolved_schema = {}
|
29
29
|
for key, value in schema.items():
|
30
30
|
# If $ref exists, resolve the reference
|
31
|
-
if key ==
|
32
|
-
ref_path = schema[
|
33
|
-
ref_name = ref_path.split(
|
31
|
+
if key == "$ref":
|
32
|
+
ref_path = schema["$ref"]
|
33
|
+
ref_name = ref_path.split("/")[
|
34
|
+
-1
|
35
|
+
] # Extract the reference name from $defs/Req -> Req
|
34
36
|
resolved = definitions.get(ref_name)
|
35
37
|
if resolved:
|
36
|
-
resolved_schema |= resolve_refs(
|
38
|
+
resolved_schema |= resolve_refs(
|
39
|
+
copy.deepcopy(resolved), definitions
|
40
|
+
)
|
37
41
|
else:
|
38
42
|
resolved_schema[key] = resolve_refs(value, definitions)
|
39
43
|
return resolved_schema
|
@@ -1,25 +1,29 @@
|
|
1
1
|
import inspect
|
2
|
-
from inspect import
|
3
|
-
from typing import Any, Dict,
|
2
|
+
from inspect import Parameter, signature
|
3
|
+
from typing import Any, Dict, Tuple, Type
|
4
4
|
|
5
5
|
from pydantic import BaseModel, create_model
|
6
6
|
from pydantic.fields import FieldInfo
|
7
7
|
|
8
8
|
from hyperpocket.config import pocket_logger
|
9
|
-
from hyperpocket.util.extract_func_param_desc_from_docstring import
|
9
|
+
from hyperpocket.util.extract_func_param_desc_from_docstring import (
|
10
|
+
extract_param_docstring_mapping,
|
11
|
+
)
|
10
12
|
|
11
13
|
|
12
14
|
def function_to_model(func: callable) -> Type[BaseModel]:
|
13
15
|
docstring = inspect.getdoc(func)
|
14
16
|
if docstring is None:
|
15
|
-
pocket_logger.info(
|
17
|
+
pocket_logger.info(
|
18
|
+
"not found docstring. use function name as description instead."
|
19
|
+
)
|
16
20
|
docstring = func.__name__
|
17
21
|
fields: Dict[str, Tuple[Type, Any]] = {}
|
18
22
|
sig = signature(func)
|
19
23
|
param_desc_map = extract_param_docstring_mapping(func)
|
20
24
|
|
21
25
|
for param_name, param in sig.parameters.items():
|
22
|
-
if param_name in (
|
26
|
+
if param_name in ("self", "cls"):
|
23
27
|
continue
|
24
28
|
|
25
29
|
if param.kind == Parameter.VAR_POSITIONAL:
|
@@ -31,19 +35,36 @@ def function_to_model(func: callable) -> Type[BaseModel]:
|
|
31
35
|
continue
|
32
36
|
|
33
37
|
if param.annotation is Parameter.empty:
|
34
|
-
raise Exception(
|
38
|
+
raise Exception(
|
39
|
+
f"Should all arguments be annotated but {param_name} is not annotated"
|
40
|
+
)
|
35
41
|
|
36
|
-
if
|
42
|
+
if (
|
43
|
+
param.annotation.__module__ == "typing"
|
44
|
+
and param.annotation.__name__ == "Optional"
|
45
|
+
):
|
37
46
|
fields[param_name] = (
|
38
|
-
param.annotation.__args__[0],
|
39
|
-
|
47
|
+
param.annotation.__args__[0],
|
48
|
+
FieldInfo(
|
49
|
+
default=param.default,
|
50
|
+
description=param_desc_map.get(param_name, ""),
|
51
|
+
),
|
52
|
+
)
|
53
|
+
elif param.annotation.__module__ != "builtins" and not issubclass(
|
54
|
+
param.annotation, BaseModel
|
55
|
+
):
|
40
56
|
raise Exception(
|
41
|
-
f"currently only support builtin types and pydantic BaseModel but {param_name} is not builtin type"
|
57
|
+
f"currently only support builtin types and pydantic BaseModel but {param_name} is not builtin type"
|
58
|
+
)
|
42
59
|
|
43
60
|
default = param.default if param.default is not Parameter.empty else ...
|
44
61
|
|
45
62
|
fields[param_name] = (
|
46
|
-
param.annotation,
|
63
|
+
param.annotation,
|
64
|
+
FieldInfo(default=default, description=param_desc_map.get(param_name, "")),
|
65
|
+
)
|
47
66
|
|
48
|
-
model = create_model(
|
67
|
+
model = create_model(
|
68
|
+
f"{func.__name__.capitalize()}Model", **fields, __doc__=docstring
|
69
|
+
)
|
49
70
|
return model
|
@@ -1,10 +1,12 @@
|
|
1
1
|
from typing import Type, Union
|
2
2
|
|
3
|
-
from pydantic import BaseModel,
|
3
|
+
from pydantic import BaseModel, Field, create_model
|
4
4
|
|
5
5
|
|
6
6
|
# Convert JSON Schema to a Pydantic model
|
7
|
-
def json_schema_to_model(
|
7
|
+
def json_schema_to_model(
|
8
|
+
schema: dict, model_name: str = "DynamicModel"
|
9
|
+
) -> Type[BaseModel]:
|
8
10
|
"""Recursively create a Pydantic model from a JSON Schema."""
|
9
11
|
fields = {}
|
10
12
|
config_extra = "forbid"
|
@@ -16,12 +18,16 @@ def json_schema_to_model(schema: dict, model_name: str = "DynamicModel") -> Type
|
|
16
18
|
if "anyOf" in property_schema:
|
17
19
|
types = []
|
18
20
|
for item in property_schema["anyOf"]:
|
19
|
-
sub_type = _convert_to_python_type(
|
21
|
+
sub_type = _convert_to_python_type(
|
22
|
+
item["type"], model_name, property_schema
|
23
|
+
)
|
20
24
|
types.append(sub_type)
|
21
25
|
|
22
26
|
field_type = Union[tuple(types)]
|
23
27
|
elif "type" in property_schema:
|
24
|
-
field_type = _convert_to_python_type(
|
28
|
+
field_type = _convert_to_python_type(
|
29
|
+
property_schema["type"], model_name, property_schema
|
30
|
+
)
|
25
31
|
else:
|
26
32
|
raise RuntimeError("have no type in json schema.")
|
27
33
|
|
@@ -35,7 +41,10 @@ def json_schema_to_model(schema: dict, model_name: str = "DynamicModel") -> Type
|
|
35
41
|
if required:
|
36
42
|
fields[property_name] = (field_type, Field(description=field_description))
|
37
43
|
else:
|
38
|
-
fields[property_name] = (
|
44
|
+
fields[property_name] = (
|
45
|
+
field_type,
|
46
|
+
Field(default=field_default, description=field_description),
|
47
|
+
)
|
39
48
|
|
40
49
|
# Handle additionalProperties
|
41
50
|
if "additionalProperties" in schema:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hyperpocket
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.2.0
|
4
4
|
Summary: Building AI agent with hyperpocket tool in a flash
|
5
5
|
Project-URL: Homepage, https://vessl-ai.github.io/hyperpocket
|
6
6
|
Project-URL: Repository, https://github.com/vessl-ai/hyperpocket
|
@@ -23,13 +23,14 @@ Requires-Dist: toml>=0.10.2
|
|
23
23
|
Requires-Dist: uvicorn>=0.32.1
|
24
24
|
Description-Content-Type: text/markdown
|
25
25
|
|
26
|
+
<p align="center">
|
27
|
+
<img src="../../logo.png" alt="hyperpocket" width="570"/>
|
28
|
+
</p>
|
29
|
+
|
26
30
|
# Hyperpocket 👛
|
27
31
|
|
28
32
|
Hyperpocket is where tools belong. Power your agent up with a pocket of tools. 👛
|
29
33
|
|
30
|
-
<figure>
|
31
|
-
<img src="../../logo.png" alt="hyperpocket" width="200"/>
|
32
|
-
</figure>
|
33
34
|
|
34
35
|
## Introduction
|
35
36
|
|
@@ -160,13 +161,15 @@ graph_builder.compile()
|
|
160
161
|
```
|
161
162
|
|
162
163
|
```python
|
164
|
+
import os
|
165
|
+
|
163
166
|
from llama_index.core.agent import FunctionCallingAgent
|
164
167
|
from llama_index.llms.openai import OpenAI
|
165
168
|
|
166
169
|
from hyperpocket.config import secret
|
167
170
|
from hyperpocket_llamaindex import PocketLlamaindex
|
168
171
|
|
169
|
-
llm = OpenAI(api_key=
|
172
|
+
llm = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
170
173
|
pocket = PocketLlamaindex(
|
171
174
|
tools=[
|
172
175
|
"https://github.com/vessl-ai/hyperpocket/tree/main/tools/slack/get-message",
|
@@ -318,3 +321,6 @@ scopes = []
|
|
318
321
|
)
|
319
322
|
def my_function(**kwargs):
|
320
323
|
```
|
324
|
+
|
325
|
+
## Special thanks
|
326
|
+
- [tott](https://x.com/tott____) for drawing the cute possum in a pocket.
|