hyperpocket 0.1.8__py3-none-any.whl → 0.1.10__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- hyperpocket/config/settings.py +12 -8
- hyperpocket/pocket_core.py +4 -2
- hyperpocket/pocket_main.py +3 -1
- hyperpocket/repository/lock.py +52 -1
- hyperpocket/server/server.py +8 -8
- hyperpocket/tool/function/tool.py +31 -37
- hyperpocket/tool/tool.py +42 -33
- hyperpocket/tool/wasm/browser.py +2 -7
- hyperpocket/util/extract_func_param_desc_from_docstring.py +11 -8
- hyperpocket/util/json_schema_to_model.py +2 -0
- {hyperpocket-0.1.8.dist-info → hyperpocket-0.1.10.dist-info}/METADATA +27 -38
- {hyperpocket-0.1.8.dist-info → hyperpocket-0.1.10.dist-info}/RECORD +14 -16
- hyperpocket/tool/tests/test_function_tool.py +0 -321
- hyperpocket/tool/tests/test_wasm_tool.py +0 -73
- {hyperpocket-0.1.8.dist-info → hyperpocket-0.1.10.dist-info}/WHEEL +0 -0
- {hyperpocket-0.1.8.dist-info → hyperpocket-0.1.10.dist-info}/entry_points.txt +0 -0
hyperpocket/config/settings.py
CHANGED
@@ -7,18 +7,22 @@ from pydantic import BaseModel, Field
|
|
7
7
|
from hyperpocket.config.auth import AuthConfig, DefaultAuthConfig
|
8
8
|
from hyperpocket.config.session import DefaultSessionConfig, SessionConfig
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
POCKET_ROOT = Path.home() / ".pocket"
|
11
|
+
SETTING_ROOT = Path.cwd()
|
12
|
+
|
13
|
+
|
14
|
+
settings_path = SETTING_ROOT / "settings.toml"
|
14
15
|
if not settings_path.exists():
|
15
16
|
with open(settings_path, "w"):
|
16
17
|
pass
|
17
|
-
|
18
|
+
|
19
|
+
secret_path = SETTING_ROOT / ".secrets.toml"
|
18
20
|
if not secret_path.exists():
|
19
21
|
with open(secret_path, "w"):
|
20
22
|
pass
|
21
|
-
|
23
|
+
|
24
|
+
|
25
|
+
toolpkg_path = POCKET_ROOT / "toolpkg"
|
22
26
|
if not toolpkg_path.exists():
|
23
27
|
os.makedirs(toolpkg_path)
|
24
28
|
|
@@ -50,9 +54,9 @@ class Config(BaseModel):
|
|
50
54
|
|
51
55
|
@property
|
52
56
|
def public_base_url(self):
|
53
|
-
if self.public_server_protocol ==
|
57
|
+
if self.public_server_protocol == "https" and self.public_server_port == 443:
|
54
58
|
return f"{self.public_server_protocol}://{self.public_hostname}"
|
55
|
-
elif self.public_server_protocol ==
|
59
|
+
elif self.public_server_protocol == "http" and self.public_server_port == 80:
|
56
60
|
return f"{self.public_server_protocol}://{self.public_hostname}"
|
57
61
|
return f"{self.public_server_protocol}://{self.public_hostname}:{self.public_server_port}"
|
58
62
|
|
hyperpocket/pocket_core.py
CHANGED
@@ -38,8 +38,10 @@ class PocketCore:
|
|
38
38
|
lock = LocalLock(tool_like)
|
39
39
|
req = WasmToolRequest(lock, "")
|
40
40
|
else:
|
41
|
-
|
42
|
-
|
41
|
+
base_repo_url, git_ref, rel_path = GitLock.parse_repo_url(repo_url=tool_like)
|
42
|
+
lock = GitLock(repository_url=base_repo_url, git_ref=git_ref)
|
43
|
+
req = WasmToolRequest(lock=lock, rel_path=rel_path, tool_vars={})
|
44
|
+
|
43
45
|
lockfile.add_lock(lock)
|
44
46
|
tool_likes.append(req)
|
45
47
|
elif isinstance(tool_like, WasmToolRequest):
|
hyperpocket/pocket_main.py
CHANGED
@@ -16,7 +16,9 @@ class Pocket(object):
|
|
16
16
|
tools: list[ToolLike],
|
17
17
|
auth: PocketAuth = None,
|
18
18
|
lockfile_path: Optional[str] = None,
|
19
|
-
force_update: bool = False
|
19
|
+
force_update: bool = False,
|
20
|
+
use_profile: bool = False):
|
21
|
+
self.use_profile = use_profile
|
20
22
|
|
21
23
|
self.core = PocketCore(
|
22
24
|
tools=tools,
|
hyperpocket/repository/lock.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import abc
|
2
2
|
import pathlib
|
3
3
|
import shutil
|
4
|
-
from typing import Optional
|
4
|
+
from typing import Optional, Tuple
|
5
5
|
|
6
6
|
import git
|
7
7
|
from pydantic import BaseModel, Field
|
@@ -159,6 +159,57 @@ class GitLock(Lock):
|
|
159
159
|
break
|
160
160
|
return new_sha
|
161
161
|
|
162
|
+
@classmethod
|
163
|
+
def get_git_branches(cls, repo_url):
|
164
|
+
ls_lists = git.cmd.Git().ls_remote(repo_url)
|
165
|
+
|
166
|
+
branches = {}
|
167
|
+
for line in ls_lists.split("\n"):
|
168
|
+
sha, ref = line.split("\t")
|
169
|
+
if ref.startswith("refs/heads/"):
|
170
|
+
branch_name = ref.replace("refs/heads/", "")
|
171
|
+
branches[branch_name] = sha
|
172
|
+
|
173
|
+
return branches
|
174
|
+
|
175
|
+
@classmethod
|
176
|
+
def parse_repo_url(cls, repo_url: str) -> Tuple[str, str, str]:
|
177
|
+
"""
|
178
|
+
Parses a GitHub repository URL with optional branch and path information.
|
179
|
+
|
180
|
+
Returns:
|
181
|
+
Tuple[str, str, str]: base_repo, branch_name, directory_path
|
182
|
+
"""
|
183
|
+
if not repo_url.startswith("https://github.com/"):
|
184
|
+
raise AttributeError("Only GitHub URLs are supported")
|
185
|
+
|
186
|
+
# Remove the base URL and split the path
|
187
|
+
repo_path = repo_url.removeprefix("https://github.com/")
|
188
|
+
repo_path_list = repo_path.split("/")
|
189
|
+
|
190
|
+
# Check if the URL contains 'tree' (indicating branch and sub-path information)
|
191
|
+
if "tree" not in repo_path_list:
|
192
|
+
# If no 'tree', return the full repository URL
|
193
|
+
return repo_url, "HEAD", ""
|
194
|
+
|
195
|
+
# Parse base repo URL and remaining path
|
196
|
+
tree_index = repo_path_list.index("tree")
|
197
|
+
base_repo = f"https://github.com/{'/'.join(repo_path_list[:tree_index])}"
|
198
|
+
sub_path = repo_path_list[tree_index + 1:]
|
199
|
+
|
200
|
+
# Fetch branch information
|
201
|
+
branches = cls.get_git_branches(base_repo)
|
202
|
+
|
203
|
+
# Find branch and sub-directory path
|
204
|
+
for idx in range(1, len(sub_path) + 1):
|
205
|
+
branch_name = "/".join(sub_path[:idx])
|
206
|
+
if branch_name in branches:
|
207
|
+
directory_path = "/".join(sub_path[idx:]) if idx < len(sub_path) else None
|
208
|
+
return base_repo, branch_name, directory_path
|
209
|
+
|
210
|
+
# If no valid branch is found, raise an error
|
211
|
+
raise ValueError("Branch not found in repository")
|
212
|
+
|
162
213
|
def eject_to_path(self, dest_path: pathlib.Path, src_sub_path: str = None):
|
163
214
|
|
164
215
|
# clone the git repository to the target path
|
hyperpocket/server/server.py
CHANGED
@@ -136,11 +136,11 @@ class PocketServer(object):
|
|
136
136
|
while True:
|
137
137
|
if conn.poll():
|
138
138
|
op, uid, result, error = conn.recv()
|
139
|
-
if error:
|
140
|
-
raise error
|
141
|
-
|
142
139
|
future = self.future_store[uid]
|
143
|
-
|
140
|
+
if error:
|
141
|
+
future.set_exception(error)
|
142
|
+
else:
|
143
|
+
future.set_result(result)
|
144
144
|
break
|
145
145
|
else:
|
146
146
|
await asyncio.sleep(0)
|
@@ -158,7 +158,7 @@ class PocketServer(object):
|
|
158
158
|
self._set_mp_start_method()
|
159
159
|
|
160
160
|
self.pipe = mp.Pipe()
|
161
|
-
self.process = mp.Process(target=self._run, args=(pocket_core,)
|
161
|
+
self.process = mp.Process(target=self._run, args=(pocket_core,))
|
162
162
|
self.process.start()
|
163
163
|
|
164
164
|
def _run(self, pocket_core):
|
@@ -185,9 +185,9 @@ class PocketServer(object):
|
|
185
185
|
from hyperpocket.server.proxy import _generate_ssl_certificates
|
186
186
|
from hyperpocket.server.proxy import https_proxy_app
|
187
187
|
|
188
|
-
from hyperpocket.config.settings import
|
189
|
-
ssl_keypath =
|
190
|
-
ssl_certpath =
|
188
|
+
from hyperpocket.config.settings import POCKET_ROOT
|
189
|
+
ssl_keypath = POCKET_ROOT / "callback_server.key"
|
190
|
+
ssl_certpath = POCKET_ROOT / "callback_server.crt"
|
191
191
|
|
192
192
|
if not ssl_keypath.exists() or not ssl_certpath.exists():
|
193
193
|
_generate_ssl_certificates(ssl_keypath, ssl_certpath)
|
@@ -1,11 +1,9 @@
|
|
1
1
|
import asyncio
|
2
2
|
import copy
|
3
3
|
import inspect
|
4
|
-
import pathlib
|
5
4
|
from typing import Any, Coroutine
|
6
5
|
from typing import Callable, Optional
|
7
6
|
|
8
|
-
import toml
|
9
7
|
from pydantic import BaseModel
|
10
8
|
|
11
9
|
from hyperpocket.tool.tool import Tool, ToolAuth
|
@@ -47,32 +45,37 @@ class FunctionTool(Tool):
|
|
47
45
|
_kwargs = copy.deepcopy(kwargs)
|
48
46
|
|
49
47
|
# make body args to model
|
50
|
-
schema_model = self.schema_model()
|
51
|
-
model = schema_model(
|
48
|
+
schema_model = self.schema_model(use_profile=False)
|
49
|
+
model = schema_model(**_kwargs["body"])
|
52
50
|
_kwargs.pop("body")
|
53
51
|
|
54
52
|
# body model to dict
|
55
|
-
args = self.model_to_kwargs(model
|
53
|
+
args = self.model_to_kwargs(model)
|
56
54
|
|
57
55
|
# binding args
|
58
56
|
binding_args = {}
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
57
|
+
if self.func.__dict__.get("__model__") is not None:
|
58
|
+
# when a function signature is not inferrable from the function itself
|
59
|
+
binding_args = args.copy()
|
60
|
+
binding_args |= _kwargs.get("envs", {}) | self.tool_vars
|
61
|
+
else:
|
62
|
+
sig = inspect.signature(self.func)
|
63
|
+
for param_name, param in sig.parameters.items():
|
64
|
+
if param_name not in args:
|
65
|
+
continue
|
63
66
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
67
|
+
if param.kind == param.VAR_KEYWORD:
|
68
|
+
# var keyword args should be passed by plain dict
|
69
|
+
binding_args |= args[param_name]
|
70
|
+
binding_args |= _kwargs.get("envs", {}) | self.tool_vars
|
68
71
|
|
69
|
-
|
70
|
-
|
72
|
+
if "envs" in _kwargs:
|
73
|
+
_kwargs.pop("envs")
|
71
74
|
|
72
|
-
|
73
|
-
|
75
|
+
binding_args |= _kwargs # add other kwargs
|
76
|
+
continue
|
74
77
|
|
75
|
-
|
78
|
+
binding_args[param_name] = args[param_name]
|
76
79
|
|
77
80
|
return binding_args
|
78
81
|
|
@@ -113,7 +116,7 @@ class FunctionTool(Tool):
|
|
113
116
|
func=func,
|
114
117
|
afunc=afunc,
|
115
118
|
name=func.__name__,
|
116
|
-
description=
|
119
|
+
description=func.__doc__ if func.__doc__ is not None else "",
|
117
120
|
argument_json_schema=argument_json_schema,
|
118
121
|
auth=auth,
|
119
122
|
default_tool_vars=tool_vars
|
@@ -123,10 +126,16 @@ class FunctionTool(Tool):
|
|
123
126
|
def from_dock(
|
124
127
|
cls,
|
125
128
|
dock: list[Callable[..., str]],
|
129
|
+
tool_vars: Optional[dict[str, str]] = None,
|
126
130
|
) -> list["FunctionTool"]:
|
131
|
+
if tool_vars is None:
|
132
|
+
tool_vars = dict()
|
127
133
|
tools = []
|
128
134
|
for func in dock:
|
129
|
-
|
135
|
+
if (_model := func.__dict__.get("__model__")) is not None:
|
136
|
+
model = _model
|
137
|
+
else:
|
138
|
+
model = function_to_model(func)
|
130
139
|
argument_json_schema = flatten_json_schema(model.model_json_schema())
|
131
140
|
if not callable(func):
|
132
141
|
raise ValueError(f"Dock element should be a list of functions, but found {func}")
|
@@ -142,6 +151,7 @@ class FunctionTool(Tool):
|
|
142
151
|
description=func.__doc__,
|
143
152
|
argument_json_schema=argument_json_schema,
|
144
153
|
auth=auth,
|
154
|
+
default_tool_vars=(tool_vars | func.__dict__.get("__vars__", {})),
|
145
155
|
))
|
146
156
|
else:
|
147
157
|
tools.append(cls(
|
@@ -151,22 +161,6 @@ class FunctionTool(Tool):
|
|
151
161
|
description=func.__doc__,
|
152
162
|
argument_json_schema=argument_json_schema,
|
153
163
|
auth=auth,
|
164
|
+
default_tool_vars=(tool_vars | func.__dict__.get("__vars__", {})),
|
154
165
|
))
|
155
166
|
return tools
|
156
|
-
|
157
|
-
@classmethod
|
158
|
-
def _get_tool_vars_from_config(cls, func: Callable) -> dict:
|
159
|
-
print(func.__name__)
|
160
|
-
tool_path = inspect.getfile(func)
|
161
|
-
print(tool_path)
|
162
|
-
tool_parent = "/".join(tool_path.split("/")[:-1])
|
163
|
-
tool_config_path = pathlib.Path(tool_parent) / "config.toml"
|
164
|
-
with tool_config_path.open("r") as f:
|
165
|
-
tool_config = toml.load(f)
|
166
|
-
tool_vars = tool_config.get("tool_var")
|
167
|
-
if not tool_vars:
|
168
|
-
return
|
169
|
-
tool_vars_dict = {}
|
170
|
-
for key, value in tool_vars.items():
|
171
|
-
tool_vars_dict[key] = value
|
172
|
-
return tool_vars_dict
|
hyperpocket/tool/tool.py
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
import abc
|
2
|
-
import pathlib
|
3
|
-
|
4
|
-
import toml
|
5
2
|
from typing import Optional, Type, Callable
|
6
3
|
|
7
4
|
from pydantic import BaseModel, Field
|
8
5
|
|
9
6
|
from hyperpocket.auth.provider import AuthProvider
|
10
7
|
from hyperpocket.config.logger import pocket_logger
|
8
|
+
from hyperpocket.prompts import pocket_extended_tool_description
|
11
9
|
from hyperpocket.util.json_schema_to_model import json_schema_to_model
|
12
10
|
|
13
11
|
|
@@ -36,7 +34,7 @@ class ToolRequest(abc.ABC):
|
|
36
34
|
@abc.abstractmethod
|
37
35
|
def __str__(self):
|
38
36
|
raise NotImplementedError
|
39
|
-
|
37
|
+
|
40
38
|
def add_postprocessing(self, postprocessing: Callable):
|
41
39
|
if self.postprocessings is None:
|
42
40
|
self.postprocessings = [postprocessing]
|
@@ -67,9 +65,11 @@ class Tool(BaseModel, abc.ABC):
|
|
67
65
|
description: str = Field(description="tool description")
|
68
66
|
argument_json_schema: Optional[dict] = Field(default=None, description="tool argument json schema")
|
69
67
|
auth: Optional[ToolAuth] = Field(default=None, description="authentication information to invoke tool")
|
70
|
-
postprocessings: Optional[list[Callable]] = Field(default=None,
|
68
|
+
postprocessings: Optional[list[Callable]] = Field(default=None,
|
69
|
+
description="postprocessing functions after tool is invoked")
|
71
70
|
default_tool_vars: dict[str, str] = Field(default_factory=dict, description="default tool variables")
|
72
71
|
overridden_tool_vars: dict[str, str] = Field(default_factory=dict, description="overridden tool variables")
|
72
|
+
use_profile: bool = False
|
73
73
|
|
74
74
|
@abc.abstractmethod
|
75
75
|
def invoke(self, **kwargs) -> str:
|
@@ -84,17 +84,23 @@ class Tool(BaseModel, abc.ABC):
|
|
84
84
|
"""
|
85
85
|
raise NotImplementedError()
|
86
86
|
|
87
|
-
def schema_model(self) -> Optional[Type[BaseModel]]:
|
87
|
+
def schema_model(self, use_profile: bool = False) -> Optional[Type[BaseModel]]:
|
88
88
|
"""
|
89
89
|
Returns a schema_model that wraps the existing argument_json_schema
|
90
90
|
to include profile and thread_id as arguments when the tool is invoked
|
91
91
|
"""
|
92
|
-
return self._get_schema_model(self.name, self.argument_json_schema)
|
93
|
-
|
92
|
+
return self._get_schema_model(self.name, self.argument_json_schema, use_profile=use_profile)
|
93
|
+
|
94
|
+
def get_description(self, use_profile: bool = False) -> str:
|
95
|
+
if use_profile:
|
96
|
+
return pocket_extended_tool_description(self.description)
|
97
|
+
else:
|
98
|
+
return self.description
|
99
|
+
|
94
100
|
def override_tool_variables(self, override_vars: dict[str, str]) -> 'Tool':
|
95
101
|
self.overridden_tool_vars = override_vars
|
96
102
|
return self
|
97
|
-
|
103
|
+
|
98
104
|
@property
|
99
105
|
def tool_vars(self) -> dict[str, str]:
|
100
106
|
return self.default_tool_vars | self.overridden_tool_vars
|
@@ -107,37 +113,40 @@ class Tool(BaseModel, abc.ABC):
|
|
107
113
|
raise ValueError('Unknown tool request type')
|
108
114
|
|
109
115
|
@classmethod
|
110
|
-
def _get_schema_model(cls, name: str, json_schema: Optional[dict]) -> Optional[Type[BaseModel]]:
|
116
|
+
def _get_schema_model(cls, name: str, json_schema: Optional[dict], use_profile: bool) -> Optional[Type[BaseModel]]:
|
111
117
|
try:
|
112
118
|
if not json_schema:
|
113
119
|
pocket_logger.info(f"{name} tool's json_schema is none.")
|
114
120
|
return None
|
115
121
|
if 'description' not in json_schema:
|
116
122
|
json_schema['description'] = 'The argument of the tool.'
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
'
|
122
|
-
|
123
|
-
'
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
'
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
123
|
+
|
124
|
+
if use_profile:
|
125
|
+
json_schema = {
|
126
|
+
'title': name,
|
127
|
+
'type': 'object',
|
128
|
+
'properties': {
|
129
|
+
'thread_id': {
|
130
|
+
'type': 'string',
|
131
|
+
'default': 'default',
|
132
|
+
'description': 'The ID of the chat thread where the tool is invoked. Omitted when unknown.',
|
133
|
+
},
|
134
|
+
'profile': {
|
135
|
+
'type': 'string',
|
136
|
+
'default': 'default',
|
137
|
+
'description': '''The profile of the user invoking the tool. Inferred from user's messages.
|
138
|
+
Users can request tools to be invoked in specific personas, which is called a profile.
|
139
|
+
If the user's profile name can be inferred from the query, pass it as a string in the 'profile'
|
140
|
+
JSON property. Omitted when unknown.''',
|
141
|
+
},
|
142
|
+
'body': json_schema
|
133
143
|
},
|
134
|
-
'
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
model = json_schema_to_model(extended_schema, name)
|
144
|
+
'required': [
|
145
|
+
'body',
|
146
|
+
]
|
147
|
+
}
|
148
|
+
|
149
|
+
model = json_schema_to_model(json_schema, name)
|
141
150
|
return model
|
142
151
|
except Exception as e:
|
143
152
|
pocket_logger.warning(f"failed to get tool({name}) schema model. error : {e}")
|
hyperpocket/tool/wasm/browser.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import asyncio
|
2
|
+
import os
|
2
3
|
|
3
4
|
from playwright.async_api import async_playwright, Page, Playwright, BrowserContext, Route
|
4
5
|
|
@@ -13,15 +14,9 @@ class InvokerBrowser(object):
|
|
13
14
|
raise RuntimeError("Use InvokerBrowser.get_instance() instead")
|
14
15
|
|
15
16
|
async def _async_init(self):
|
16
|
-
# false only in dev
|
17
|
-
# TODO(moon.dev) : load from config by environment
|
18
|
-
import os
|
19
|
-
pocket_env = os.getenv("POCKET_ENV", "DEVELOPMENT")
|
20
|
-
is_headless = False if pocket_env == "DEVELOPMENT" else True
|
21
|
-
|
22
17
|
self.playwright = await async_playwright().start()
|
23
18
|
self.browser_context = await self.playwright.chromium.launch_persistent_context(
|
24
|
-
headless=
|
19
|
+
headless=True,
|
25
20
|
args=[
|
26
21
|
'--disable-web-security=True',
|
27
22
|
],
|
@@ -23,14 +23,12 @@ def extract_param_docstring_mapping(func) -> dict[str, str]:
|
|
23
23
|
if not docstring:
|
24
24
|
return {}
|
25
25
|
|
26
|
-
pocket_logger.debug(f"try to extract docstring of {func.__name__} by google style..")
|
27
26
|
param_mapping = extract_param_desc_by_google_stype_docstring(docstring, func_params)
|
28
27
|
if param_mapping:
|
29
28
|
pocket_logger.debug(f"success extract docstring of {func.__name__} by google style!")
|
30
29
|
return param_mapping
|
31
30
|
pocket_logger.debug(f"not found param desc of {func.__name__} by google style..")
|
32
31
|
|
33
|
-
pocket_logger.debug(f"try to extract docstring of {func.__name__} by other style..")
|
34
32
|
param_mapping = extract_param_desc_by_other_styles(docstring, func_params)
|
35
33
|
if param_mapping:
|
36
34
|
pocket_logger.debug(f"success extract docstring of {func.__name__} by other style!")
|
@@ -38,7 +36,6 @@ def extract_param_docstring_mapping(func) -> dict[str, str]:
|
|
38
36
|
pocket_logger.debug(f"not found param desc of {func.__name__} by other styles..")
|
39
37
|
|
40
38
|
# Plain Text Style matching
|
41
|
-
pocket_logger.debug(f"try to extract docstring of {func.__name__} by plain text style..")
|
42
39
|
param_descriptions = []
|
43
40
|
for line in docstring.split("\n"):
|
44
41
|
split_line = line.strip().split(":")
|
@@ -46,7 +43,8 @@ def extract_param_docstring_mapping(func) -> dict[str, str]:
|
|
46
43
|
continue
|
47
44
|
|
48
45
|
param_name = split_line[0]
|
49
|
-
cleaned_param_name =
|
46
|
+
cleaned_param_name = clean_string(param_name)
|
47
|
+
cleaned_param_name = clean_bracket_content(cleaned_param_name)
|
50
48
|
description = ":".join(split_line[1:]).strip()
|
51
49
|
if cleaned_param_name in func_params:
|
52
50
|
param_descriptions.append((cleaned_param_name, description))
|
@@ -58,6 +56,11 @@ def extract_param_docstring_mapping(func) -> dict[str, str]:
|
|
58
56
|
return param_mapping
|
59
57
|
|
60
58
|
|
59
|
+
def clean_string(input_string):
|
60
|
+
cleaned = re.sub(r"^[^a-zA-Z_]*|[^a-zA-Z0-9_()\s]*$", "", input_string)
|
61
|
+
return cleaned.strip()
|
62
|
+
|
63
|
+
|
61
64
|
def clean_bracket_content(content):
|
62
65
|
return re.sub(r"[(\[{<].*?[)\]}>]", "", content)
|
63
66
|
|
@@ -65,9 +68,9 @@ def clean_bracket_content(content):
|
|
65
68
|
def extract_param_desc_by_other_styles(docstring, func_params) -> dict[str, str]:
|
66
69
|
param_descriptions = []
|
67
70
|
# Pattern for Sphinx-style or Javadoc-style `:param`, `@param`, `:arg`, `@arg`
|
68
|
-
param_pattern = r"(?:@param|:param|:arg|@arg)
|
69
|
-
matches = re.findall(param_pattern, docstring)
|
70
|
-
for param, desc in matches:
|
71
|
+
param_pattern = r"^\s*(?:@param|:param|:arg|@arg):?\s+(\w+)(?:\((.*?)\))?:?\s*(.*)"
|
72
|
+
matches = re.findall(param_pattern, docstring, re.MULTILINE)
|
73
|
+
for param, _, desc in matches:
|
71
74
|
cleaned_param = clean_bracket_content(param)
|
72
75
|
param_descriptions.append((cleaned_param, desc.strip()))
|
73
76
|
# Ensure no duplicates and match with function parameters
|
@@ -87,7 +90,7 @@ def extract_param_desc_by_google_stype_docstring(docstring, func_params) -> dict
|
|
87
90
|
param_descriptions = {}
|
88
91
|
for line in param_lines:
|
89
92
|
# Match parameter line with "name (type): description"
|
90
|
-
param_match = re.match(r"
|
93
|
+
param_match = re.match(r"^[^a-zA-Z_]*([a-zA-Z_]\w*)\s*[\(\[]\s*(.*?)\s*[\)\]]\s*:\s*(.*)", line)
|
91
94
|
if param_match:
|
92
95
|
param, _, desc = param_match.groups()
|
93
96
|
cleaned_param = clean_bracket_content(param)
|
@@ -70,6 +70,8 @@ def _convert_to_python_type(json_type, model_name, property_schema):
|
|
70
70
|
field_type = str
|
71
71
|
elif json_type == "boolean":
|
72
72
|
field_type = bool
|
73
|
+
elif json_type == "number":
|
74
|
+
field_type = float
|
73
75
|
elif json_type == "none":
|
74
76
|
field_type = type(None)
|
75
77
|
elif json_type == "object":
|
@@ -1,10 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: hyperpocket
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.10
|
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
|
7
|
-
Author-email:
|
7
|
+
Author-email: Hyperpocket Team <hyperpocket@vessl.ai>
|
8
8
|
Requires-Python: >=3.10
|
9
9
|
Requires-Dist: click>=8.1.7
|
10
10
|
Requires-Dist: dynaconf>=3.2.6
|
@@ -23,20 +23,19 @@ Requires-Dist: toml>=0.10.2
|
|
23
23
|
Requires-Dist: uvicorn>=0.32.1
|
24
24
|
Description-Content-Type: text/markdown
|
25
25
|
|
26
|
-
#
|
26
|
+
# Hyperpocket 👛
|
27
27
|
|
28
|
-
|
28
|
+
Hyperpocket is where tools belong. Power your agent up with a pocket of tools. 👛
|
29
29
|
|
30
30
|
<figure>
|
31
|
-
<img src="
|
32
|
-
<figcaption>© Doraemon</figcaption>
|
31
|
+
<img src="../../logo.png" alt="hyperpocket" width="200"/>
|
33
32
|
</figure>
|
34
33
|
|
35
34
|
## Introduction
|
36
35
|
|
37
|
-
|
36
|
+
Hyperpocket is a tool that allows you to easily use tool and auth for agents on your machine.
|
38
37
|
|
39
|
-
**_Start fast._** Just install
|
38
|
+
**_Start fast._** Just install Hyperpocket and use it. We know you don't have time to authenticate to our server.
|
40
39
|
|
41
40
|
**_Go securely._** Not like others, you are the only one who knows your secret tokens. We do NOT. All of your secret
|
42
41
|
tokens belong to your infrastructure, not ours.
|
@@ -47,11 +46,6 @@ with the dependency spaghetti.
|
|
47
46
|
|
48
47
|
**_Battery Included_** You can use popular tools and authentication providers out-of-the-box.
|
49
48
|
|
50
|
-
<figure>
|
51
|
-
<img src="pocket1.png" alt="pocket-flow" width="400"/>
|
52
|
-
<figcaption></figcaption>
|
53
|
-
</figure>
|
54
|
-
|
55
49
|
## Installation
|
56
50
|
|
57
51
|
1. install hyperpocket
|
@@ -82,16 +76,16 @@ Or just use LLM API Clients out of the box.
|
|
82
76
|
### Using out-of-the-box tools
|
83
77
|
|
84
78
|
```python
|
85
|
-
|
79
|
+
|
86
80
|
from langchain_openai import ChatOpenAI
|
87
81
|
|
88
82
|
from hyperpocket_langchain import PocketLangchain
|
89
83
|
|
90
84
|
pklc = PocketLangchain(
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
85
|
+
tools=[
|
86
|
+
"https://github.com/vessl-ai/hyperpocket/tree/main/tools/slack/get-message",
|
87
|
+
"https://github.com/vessl-ai/hyperpocket/tree/main/tools/slack/post-message",
|
88
|
+
]
|
95
89
|
)
|
96
90
|
tools = pklc.get_tools()
|
97
91
|
|
@@ -104,7 +98,7 @@ llm_tool_binding.invoke(...)
|
|
104
98
|
|
105
99
|
There are two kinds of auth process, one is using system auth(developer api key) and the other is using end user auth.
|
106
100
|
|
107
|
-
|
101
|
+
Hyperpocket provides way to use end user auth easily.
|
108
102
|
(Of course, you can also just set your STRIPE_API_KEY when using Stripe API related tools)
|
109
103
|
|
110
104
|
- Supported methods
|
@@ -130,7 +124,7 @@ Pocket provides way to use end user auth easily.
|
|
130
124
|
You can manage your auths in request-wise level. (e.g. you can use different auths for different requests)
|
131
125
|
|
132
126
|
```python
|
133
|
-
|
127
|
+
|
134
128
|
from langchain_openai import ChatOpenAI
|
135
129
|
from langgraph.graph import StateGraph, START, MessagesState
|
136
130
|
from langgraph.prebuilt import tools_condition
|
@@ -138,10 +132,10 @@ from langgraph.prebuilt import tools_condition
|
|
138
132
|
from hyperpocket_langgraph import PocketLanggraph
|
139
133
|
|
140
134
|
pklg = PocketLanggraph(
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
135
|
+
tools=[
|
136
|
+
"https://github.com/vessl-ai/hyperpocket/tree/main/tools/slack/get-message",
|
137
|
+
"https://github.com/vessl-ai/hyperpocket/tree/main/tools/slack/post-message",
|
138
|
+
],
|
145
139
|
)
|
146
140
|
llm = ChatOpenAI()
|
147
141
|
|
@@ -166,22 +160,21 @@ graph_builder.compile()
|
|
166
160
|
```
|
167
161
|
|
168
162
|
```python
|
169
|
-
from hyperpocket.config import secret
|
170
|
-
from hyperpocket.tool import from_git
|
171
163
|
from llama_index.core.agent import FunctionCallingAgent
|
172
164
|
from llama_index.llms.openai import OpenAI
|
173
165
|
|
166
|
+
from hyperpocket.config import secret
|
174
167
|
from hyperpocket_llamaindex import PocketLlamaindex
|
175
168
|
|
176
169
|
llm = OpenAI(api_key=secret["OPENAI_API_KEY"])
|
177
170
|
pocket = PocketLlamaindex(
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
171
|
+
tools=[
|
172
|
+
"https://github.com/vessl-ai/hyperpocket/tree/main/tools/slack/get-message",
|
173
|
+
"https://github.com/vessl-ai/hyperpocket/tree/main/tools/slack/post-message",
|
174
|
+
"https://github.com/vessl-ai/hyperpocket/tree/main/tools/linear/get-issues",
|
175
|
+
"https://github.com/vessl-ai/hyperpocket/tree/main/tools/google/get-calendar-events",
|
176
|
+
"https://github.com/vessl-ai/hyperpocket/tree/main/tools/google/get-calendar-list",
|
177
|
+
]
|
185
178
|
)
|
186
179
|
tools = pocket.get_tools()
|
187
180
|
|
@@ -218,12 +211,8 @@ Assistance: Here are the recent 10 messages.
|
|
218
211
|
|
219
212
|
### Config
|
220
213
|
|
221
|
-
Running `pocket config init` will create your config file in `$HOME/.pocket/settings.toml`
|
222
|
-
|
223
214
|
The `settings.toml` looks as follows.
|
224
215
|
|
225
|
-
TODO: Add `secrets.toml`.
|
226
|
-
|
227
216
|
```toml
|
228
217
|
log_level = "debug"
|
229
218
|
internal_server_port = "8000" # optional, default is 8000
|
@@ -265,7 +254,7 @@ client_secret = "" # your github client secret
|
|
265
254
|
- While creating your github OAuth app, configuring your app's `Authorization callback URL` is different for your
|
266
255
|
development environment and production environment.
|
267
256
|
- For development environment, you can use `http://localhost:8000/auth/github/callback`
|
268
|
-
- **Note**: Default port for
|
257
|
+
- **Note**: Default port for hyperpocket dev server is `8000`. If you are using a different port, make sure to
|
269
258
|
replace `8000` with your actual port number.
|
270
259
|
- For production environment, you can use `https://yourdomain.com/auth/github/callback`
|
271
260
|
- **Note**: Make sure to replace `yourdomain.com` with your actual domain name that this app will be hosted on.
|
@@ -2,8 +2,8 @@ hyperpocket/__init__.py,sha256=iaJvrZ0rgHwAndGFVv8m1Iz_DWtWEcphFPL-1D8f9SY,136
|
|
2
2
|
hyperpocket/builtin.py,sha256=FnsASGfieQKvVrFVESjvm73qsxPvCf7iiHGd7HNVpuQ,2371
|
3
3
|
hyperpocket/constants.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
hyperpocket/pocket_auth.py,sha256=-d5UvLkjvsSkem5DGrDphowckVCB3NCLt2jOw9k5U9c,16934
|
5
|
-
hyperpocket/pocket_core.py,sha256=
|
6
|
-
hyperpocket/pocket_main.py,sha256=
|
5
|
+
hyperpocket/pocket_core.py,sha256=pt_IBciSlJUOq00siVNk_qVP3FK8PoIs2Bs7ZMsmm9U,10173
|
6
|
+
hyperpocket/pocket_main.py,sha256=eSwevmX7vgRQebpNtOGG5jhVGU-DE_hTUJi-zRWAvdo,10231
|
7
7
|
hyperpocket/prompts.py,sha256=XAmTTCzCkXWK50zcmWmGA25v9-HKd_4dL5o85uisbGM,472
|
8
8
|
hyperpocket/tool_like.py,sha256=ur8oMU5p4DUYBEF5MBP3faQ7CKsOzb0lLOaL6p8JqSw,134
|
9
9
|
hyperpocket/auth/README.md,sha256=zn4QqnFZCA_4X3x8Wb6lE3OP5otYxpByZaCiUkBvaNs,11562
|
@@ -77,16 +77,16 @@ hyperpocket/config/auth.py,sha256=5Zk7OZmElIqIn7LxsT7H4g3bimBxaKpnbL2v9Fs9MlA,79
|
|
77
77
|
hyperpocket/config/git.py,sha256=CGbY7J1LyN4ccZr9CFGAQwwhjC9kvdU0On_nsAsZ1FQ,362
|
78
78
|
hyperpocket/config/logger.py,sha256=Wwl-8lllaCNLZruxXU-bcC74Ciy5SmPOX4AJE5BUzWM,2769
|
79
79
|
hyperpocket/config/session.py,sha256=CSXENtWx6Gh4DiPh4u9E-4MFYkM8JRjl2hSPV9PVfrE,801
|
80
|
-
hyperpocket/config/settings.py,sha256=
|
80
|
+
hyperpocket/config/settings.py,sha256=yXwZN7eryNWxQCK5aRDHQRTzDLATzmSEZwo-6HCxELU,2017
|
81
81
|
hyperpocket/futures/__init__.py,sha256=_pRnYZLbogkYFInA3jokkxrcEVRt6YNaBmkf_dSk3SM,136
|
82
82
|
hyperpocket/futures/futurestore.py,sha256=WIJGX-XUdB4gWFu2Trto8vd3nTiLFOrnVzQhQP9bO_U,1316
|
83
83
|
hyperpocket/repository/__init__.py,sha256=P4Ge__W5wqNgNILNzHjx7qgS8KRcbwri-nb4IPVVErs,219
|
84
|
-
hyperpocket/repository/lock.py,sha256=
|
84
|
+
hyperpocket/repository/lock.py,sha256=JhuRB1J1rpqGtN19Qeht-iM0BhFuxgl0EaGW9GzqDJA,8046
|
85
85
|
hyperpocket/repository/lockfile.py,sha256=1cNkAv5WHU9o8b6arJn9wcosSp3gHMFe11mbAv72Cgs,2187
|
86
86
|
hyperpocket/repository/repository.py,sha256=a0HA6eVA88Xq6MYe3SdqBji0U8_RuiaN2v2OYkT8nZY,1349
|
87
87
|
hyperpocket/server/__init__.py,sha256=TLqok_mBeV3VRnbZ_spwrwwbsjJ1q9o375AdBk7tKNA,89
|
88
88
|
hyperpocket/server/proxy.py,sha256=OhpAWpilM5ioKAsqInKudtvpYk56nMFeZ-dwoGAYIDA,1905
|
89
|
-
hyperpocket/server/server.py,sha256=
|
89
|
+
hyperpocket/server/server.py,sha256=KPHl9WqRAQsQB-6c1D6nFvdJxxFW0dS__ZpF56oNcNU,7838
|
90
90
|
hyperpocket/server/auth/__init__.py,sha256=IMjz9PCzD7qh4QIf2g-gWIdkDeU36jt-9F55vaHvLoM,286
|
91
91
|
hyperpocket/server/auth/calendly.py,sha256=mi9_ysn0SffhnEgaoNa2jcHWCcD_yzqkS0rvzpJG2Qg,478
|
92
92
|
hyperpocket/server/auth/github.py,sha256=cgUtdCYPhf_e51fEQgiYjyG6yuPfMV5RmltjujuWcBw,759
|
@@ -108,17 +108,15 @@ hyperpocket/session/interface.py,sha256=0o5IwXezaR5NKB6F9vfumLnrBSYv7JzY_FAvZB6u
|
|
108
108
|
hyperpocket/session/redis.py,sha256=6SHR2ZJ5E3RS-G_xzeh5ls_cPZ0NarPetlEuaAtqEAk,5467
|
109
109
|
hyperpocket/tool/README.md,sha256=vbHvP3fnfihq-H481MiSZEVJNhVoUu0djENb9tiy78c,3026
|
110
110
|
hyperpocket/tool/__init__.py,sha256=PlSewsugQ6x4BXszWvmTiW-MfVS87TALvWi4TcIgreY,346
|
111
|
-
hyperpocket/tool/tool.py,sha256=
|
111
|
+
hyperpocket/tool/tool.py,sha256=9kALM1hk_IE1f9y9DK-_e0Ya5gNuP7oni1pfHU-e2_8,7282
|
112
112
|
hyperpocket/tool/function/README.md,sha256=6Y9a8FlFjEdbrVqF0NoQ1j34VoV8Zt6Pf9-xlLIHkTc,3676
|
113
113
|
hyperpocket/tool/function/__init__.py,sha256=elshxOWjKUCKSsSSkae8GB65bZ4xG1Xa4o2GCT5QbF4,259
|
114
114
|
hyperpocket/tool/function/annotation.py,sha256=UaeawAkX3QNAWLYHCQ-V5UNZyfxpOOvbYs3w5Q8bJq4,1018
|
115
|
-
hyperpocket/tool/function/tool.py,sha256=
|
115
|
+
hyperpocket/tool/function/tool.py,sha256=EENaORdCLVz2HM7fdJhDsLCJfixDC4j2Hdf0GnJ-OOg,6034
|
116
116
|
hyperpocket/tool/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
117
|
-
hyperpocket/tool/tests/test_function_tool.py,sha256=If8M9DKkfh5voJyNU6Wdk-gqC9-ZpPEpinu8C7Mz2Zo,9569
|
118
|
-
hyperpocket/tool/tests/test_wasm_tool.py,sha256=bKcx-w__pa1Yr3ZwB8TMODn95xczH-_cRKCSUAPR8iA,2420
|
119
117
|
hyperpocket/tool/wasm/README.md,sha256=QeR7mI8RD6OJ2AXdZCpzNLBW0KUVgYQ0IqCc-wVXgFs,3896
|
120
118
|
hyperpocket/tool/wasm/__init__.py,sha256=KGOPPyA1CnbW-T9bgSpfIXf9952l_p5HJDhHGBb3b9o,71
|
121
|
-
hyperpocket/tool/wasm/browser.py,sha256=
|
119
|
+
hyperpocket/tool/wasm/browser.py,sha256=a4TbetonEvClgkfQgTxpIN6a9IxZVB1jjSD3dBF8gNg,1851
|
122
120
|
hyperpocket/tool/wasm/invoker.py,sha256=u8ZfGW871IVBaG4_XbOQHfp7QYLS093QdwVa3HOL-_k,1520
|
123
121
|
hyperpocket/tool/wasm/script.py,sha256=b0il5tTF8bcMDcfR_ME8V2TppdnPDjNK2rB1GxeH5Pk,4251
|
124
122
|
hyperpocket/tool/wasm/tool.py,sha256=GPTxgXEV9DbBbW848TqzNHvVbwk2oMmI__kUhHsuLNw,5199
|
@@ -126,14 +124,14 @@ hyperpocket/tool/wasm/templates/__init__.py,sha256=cQ-uNsO418xvv54i8bD0IrqcAKUxM
|
|
126
124
|
hyperpocket/tool/wasm/templates/node.py,sha256=8ghVQGS9L3IJGdBB8waLK_ej4FS34dCA_bwPKjm8QSU,2782
|
127
125
|
hyperpocket/tool/wasm/templates/python.py,sha256=Gi_tn3QQZjolFpbhVDXHLoj4juRLTEGsq_A-iyvTyUk,2733
|
128
126
|
hyperpocket/util/__init__.py,sha256=V36_ztskLaKQxOhW2OhrhxRFn4QCxtX3jGjAT4lqNQE,35
|
129
|
-
hyperpocket/util/extract_func_param_desc_from_docstring.py,sha256=
|
127
|
+
hyperpocket/util/extract_func_param_desc_from_docstring.py,sha256=Ud1eRp1LyEO-qI34OUpZ1Osaxhzt_r93swRYFxbfSRs,4040
|
130
128
|
hyperpocket/util/find_all_leaf_class_in_package.py,sha256=afGLqe5s7irOOPh7DI70v-utDL2a0vhNzHjtgSmDeZU,528
|
131
129
|
hyperpocket/util/find_all_subclass_in_package.py,sha256=CfsM5sWHHbFZD6M-jbJRN8Zo3m57R1E7FGg_V__HdFU,964
|
132
130
|
hyperpocket/util/flatten_json_schema.py,sha256=PXK6I1S2QDxwSGmUVEl5bbSPrjTa38GBllBQ8uKXJNQ,1587
|
133
131
|
hyperpocket/util/function_to_model.py,sha256=zPBrxtvfieJearmvJeMOeIGGLn1ymXNvL9PlMoXZbwA,2061
|
134
132
|
hyperpocket/util/get_objects_from_subpackage.py,sha256=Aq87PD_H57c2IjLS28Hf0Wu5vLVyoOtDoBvKzvQ1UPw,929
|
135
|
-
hyperpocket/util/json_schema_to_model.py,sha256=
|
136
|
-
hyperpocket-0.1.
|
137
|
-
hyperpocket-0.1.
|
138
|
-
hyperpocket-0.1.
|
139
|
-
hyperpocket-0.1.
|
133
|
+
hyperpocket/util/json_schema_to_model.py,sha256=hmXqiU67WrdHZMGELvHfKxfQ12EqPtTD8x_KezCcEFk,3465
|
134
|
+
hyperpocket-0.1.10.dist-info/METADATA,sha256=Rp9BULH23ZqLLSU2_POT_K9T_PaDkmXPpBYFzcYoNds,9577
|
135
|
+
hyperpocket-0.1.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
136
|
+
hyperpocket-0.1.10.dist-info/entry_points.txt,sha256=KpBleaYr0SaENXOa-dFvJ_cvFCHYFEQ4LMl11ShAcBI,61
|
137
|
+
hyperpocket-0.1.10.dist-info/RECORD,,
|
@@ -1,321 +0,0 @@
|
|
1
|
-
from unittest import TestCase
|
2
|
-
|
3
|
-
from pydantic import BaseModel
|
4
|
-
|
5
|
-
from hyperpocket.auth import AuthProvider
|
6
|
-
from hyperpocket.tool.function.annotation import function_tool
|
7
|
-
from hyperpocket.tool.function.tool import FunctionTool
|
8
|
-
from hyperpocket.util.flatten_json_schema import flatten_json_schema
|
9
|
-
|
10
|
-
|
11
|
-
class TestFunctionTool(TestCase):
|
12
|
-
|
13
|
-
def test_function_tool_call(self):
|
14
|
-
# given
|
15
|
-
@function_tool
|
16
|
-
def add_numbers(a: int, b: int):
|
17
|
-
"""
|
18
|
-
add two numbers
|
19
|
-
a(int): first name
|
20
|
-
b(int): second name
|
21
|
-
"""
|
22
|
-
return a + b
|
23
|
-
|
24
|
-
# when
|
25
|
-
result = add_numbers.invoke(body={
|
26
|
-
"a": 1,
|
27
|
-
"b": 2
|
28
|
-
})
|
29
|
-
|
30
|
-
# then
|
31
|
-
self.assertEqual(result, '3')
|
32
|
-
|
33
|
-
def test_function_tool_variables(self):
|
34
|
-
@function_tool(
|
35
|
-
tool_vars={
|
36
|
-
'a': '1',
|
37
|
-
'b': '2',
|
38
|
-
},
|
39
|
-
)
|
40
|
-
def always_three(**kwargs):
|
41
|
-
a = int(kwargs['a'])
|
42
|
-
b = int(kwargs['b'])
|
43
|
-
return str(a+b)
|
44
|
-
|
45
|
-
result = always_three.invoke(
|
46
|
-
body={}
|
47
|
-
)
|
48
|
-
self.assertEqual(result, '3')
|
49
|
-
|
50
|
-
def test_function_tool_overridden_variables(self):
|
51
|
-
@function_tool(
|
52
|
-
tool_vars={
|
53
|
-
'a': '1',
|
54
|
-
'b': '1',
|
55
|
-
},
|
56
|
-
)
|
57
|
-
def always_two(**kwargs):
|
58
|
-
a = int(kwargs['a'])
|
59
|
-
b = int(kwargs['b'])
|
60
|
-
return str(a+b)
|
61
|
-
|
62
|
-
always_two.override_tool_variables({
|
63
|
-
'a': '1',
|
64
|
-
'b': '2',
|
65
|
-
})
|
66
|
-
result = always_two.invoke(body={})
|
67
|
-
self.assertEqual(result, '3')
|
68
|
-
|
69
|
-
def test_function_tool_overridden_variables_from_func(self):
|
70
|
-
@function_tool(
|
71
|
-
tool_vars={
|
72
|
-
'a': '1',
|
73
|
-
'b': '1',
|
74
|
-
},
|
75
|
-
)
|
76
|
-
def always_two(**kwargs):
|
77
|
-
a = int(kwargs['a'])
|
78
|
-
b = int(kwargs['b'])
|
79
|
-
return str(a+b)
|
80
|
-
|
81
|
-
tool = FunctionTool.from_func(always_two, tool_vars={
|
82
|
-
"a": "1",
|
83
|
-
"b": "2",
|
84
|
-
})
|
85
|
-
result = tool.invoke(body={})
|
86
|
-
self.assertEqual(result, '3')
|
87
|
-
|
88
|
-
def test_pydantic_input_function_tool_call(self):
|
89
|
-
# given
|
90
|
-
class FirstNumber(BaseModel):
|
91
|
-
first: int
|
92
|
-
|
93
|
-
class SecondNumber(BaseModel):
|
94
|
-
second: int
|
95
|
-
|
96
|
-
@function_tool
|
97
|
-
def add_numbers(a: FirstNumber, b: SecondNumber):
|
98
|
-
"""
|
99
|
-
add two numbers
|
100
|
-
a(FirstNumber): first number
|
101
|
-
b(SecondNumber): second number
|
102
|
-
"""
|
103
|
-
return a.first + b.second
|
104
|
-
|
105
|
-
# when
|
106
|
-
result = add_numbers.invoke(body={
|
107
|
-
"a": {
|
108
|
-
"first": 1,
|
109
|
-
},
|
110
|
-
"b": {
|
111
|
-
"second": 2
|
112
|
-
}
|
113
|
-
})
|
114
|
-
|
115
|
-
# then
|
116
|
-
self.assertEqual(result, '3')
|
117
|
-
|
118
|
-
def test_register_no_auth_no_init_func_case(self):
|
119
|
-
"""
|
120
|
-
Test register functionTool in case of no-auth and no-init function
|
121
|
-
"""
|
122
|
-
|
123
|
-
@function_tool
|
124
|
-
def add_numbers(a: int, b: int):
|
125
|
-
"""
|
126
|
-
add two numbers
|
127
|
-
a(int): first name
|
128
|
-
b(int): second name
|
129
|
-
"""
|
130
|
-
return a + b
|
131
|
-
|
132
|
-
# when
|
133
|
-
result = add_numbers.invoke(body={
|
134
|
-
"a": 1,
|
135
|
-
"b": 2
|
136
|
-
})
|
137
|
-
|
138
|
-
# then
|
139
|
-
self.assertIsInstance(add_numbers, FunctionTool)
|
140
|
-
self.assertEqual(result, '3')
|
141
|
-
|
142
|
-
def test_register_no_auth_init_func_case(self):
|
143
|
-
"""
|
144
|
-
Test register functionTool in case of no-auth and init function
|
145
|
-
"""
|
146
|
-
|
147
|
-
@function_tool()
|
148
|
-
def add_numbers(a: int, b: int):
|
149
|
-
"""
|
150
|
-
add two numbers
|
151
|
-
a(int): first name
|
152
|
-
b(int): second name
|
153
|
-
"""
|
154
|
-
return a + b
|
155
|
-
|
156
|
-
# when
|
157
|
-
result = add_numbers.invoke(body={
|
158
|
-
"a": 1,
|
159
|
-
"b": 2
|
160
|
-
})
|
161
|
-
|
162
|
-
# then
|
163
|
-
self.assertIsInstance(add_numbers, FunctionTool)
|
164
|
-
self.assertEqual(result, '3')
|
165
|
-
|
166
|
-
def test_register_auth_init_func_case(self):
|
167
|
-
"""
|
168
|
-
Test register functionTool in case of auth and init function
|
169
|
-
|
170
|
-
"""
|
171
|
-
|
172
|
-
@function_tool(auth_provider=AuthProvider.SLACK)
|
173
|
-
def add_numbers(a: int, b: int):
|
174
|
-
"""
|
175
|
-
add two numbers
|
176
|
-
a(int): first name
|
177
|
-
b(int): second name
|
178
|
-
"""
|
179
|
-
return a + b
|
180
|
-
|
181
|
-
# when
|
182
|
-
result = add_numbers.invoke(body={
|
183
|
-
"a": 1,
|
184
|
-
"b": 2
|
185
|
-
},
|
186
|
-
envs={
|
187
|
-
"SLACK_BOT_TOKEN": "test"
|
188
|
-
})
|
189
|
-
|
190
|
-
# then
|
191
|
-
self.assertIsInstance(add_numbers, FunctionTool)
|
192
|
-
self.assertEqual(result, '3')
|
193
|
-
|
194
|
-
def test_google_style_docstring_parsing(self):
|
195
|
-
"""
|
196
|
-
Test google style docstring parsing
|
197
|
-
"""
|
198
|
-
|
199
|
-
# given
|
200
|
-
@function_tool
|
201
|
-
def add_numbers(a: int, b: int):
|
202
|
-
"""
|
203
|
-
add two numbers
|
204
|
-
|
205
|
-
Args:
|
206
|
-
a(int): first name
|
207
|
-
b(int): second name
|
208
|
-
"""
|
209
|
-
return a + b
|
210
|
-
|
211
|
-
# when
|
212
|
-
schema = add_numbers.schema_model()
|
213
|
-
schema_json = schema.model_json_schema()
|
214
|
-
flatten_schema_json = flatten_json_schema(schema_json)
|
215
|
-
func_schema = flatten_schema_json["properties"]["body"]
|
216
|
-
|
217
|
-
# then
|
218
|
-
self.assertTrue(str(func_schema["description"]).startswith("add two numbers"))
|
219
|
-
self.assertEqual(func_schema["title"], "add_numbers")
|
220
|
-
self.assertEqual(func_schema["required"], ["a", "b"])
|
221
|
-
self.assertEqual(func_schema["type"], "object")
|
222
|
-
self.assertEqual(func_schema["properties"]["a"]["type"], "integer")
|
223
|
-
self.assertEqual(func_schema["properties"]["a"]["description"], "first name")
|
224
|
-
self.assertEqual(func_schema["properties"]["b"]["type"], "integer")
|
225
|
-
self.assertEqual(func_schema["properties"]["b"]["description"], "second name")
|
226
|
-
|
227
|
-
def test_javadoc_style_docstring_parsing(self):
|
228
|
-
"""
|
229
|
-
Test javadoc style docstring parsing
|
230
|
-
"""
|
231
|
-
|
232
|
-
# given
|
233
|
-
@function_tool
|
234
|
-
def add_numbers(a: int, b: int):
|
235
|
-
"""
|
236
|
-
add two numbers
|
237
|
-
|
238
|
-
@param a first name
|
239
|
-
@param b second name
|
240
|
-
"""
|
241
|
-
return a + b
|
242
|
-
|
243
|
-
# when
|
244
|
-
schema = add_numbers.schema_model()
|
245
|
-
schema_json = schema.model_json_schema()
|
246
|
-
flatten_schema_json = flatten_json_schema(schema_json)
|
247
|
-
func_schema = flatten_schema_json["properties"]["body"]
|
248
|
-
|
249
|
-
# then
|
250
|
-
self.assertTrue(str(func_schema["description"]).startswith("add two numbers"))
|
251
|
-
self.assertEqual(func_schema["title"], "add_numbers")
|
252
|
-
self.assertEqual(func_schema["required"], ["a", "b"])
|
253
|
-
self.assertEqual(func_schema["type"], "object")
|
254
|
-
self.assertEqual(func_schema["properties"]["a"]["type"], "integer")
|
255
|
-
self.assertEqual(func_schema["properties"]["a"]["description"], "first name")
|
256
|
-
self.assertEqual(func_schema["properties"]["b"]["type"], "integer")
|
257
|
-
self.assertEqual(func_schema["properties"]["b"]["description"], "second name")
|
258
|
-
|
259
|
-
def test_sphinx_style_docstring_parsing(self):
|
260
|
-
"""
|
261
|
-
Test sphinx style docstring parsing
|
262
|
-
"""
|
263
|
-
|
264
|
-
# given
|
265
|
-
@function_tool
|
266
|
-
def add_numbers(a: int, b: int):
|
267
|
-
"""
|
268
|
-
add two numbers
|
269
|
-
|
270
|
-
:param a: first name
|
271
|
-
:param b: second name
|
272
|
-
"""
|
273
|
-
return a + b
|
274
|
-
|
275
|
-
# when
|
276
|
-
schema = add_numbers.schema_model()
|
277
|
-
schema_json = schema.model_json_schema()
|
278
|
-
flatten_schema_json = flatten_json_schema(schema_json)
|
279
|
-
func_schema = flatten_schema_json["properties"]["body"]
|
280
|
-
|
281
|
-
# then
|
282
|
-
self.assertTrue(str(func_schema["description"]).startswith("add two numbers"))
|
283
|
-
self.assertEqual(func_schema["title"], "add_numbers")
|
284
|
-
self.assertEqual(func_schema["required"], ["a", "b"])
|
285
|
-
self.assertEqual(func_schema["type"], "object")
|
286
|
-
self.assertEqual(func_schema["properties"]["a"]["type"], "integer")
|
287
|
-
self.assertEqual(func_schema["properties"]["a"]["description"], "first name")
|
288
|
-
self.assertEqual(func_schema["properties"]["b"]["type"], "integer")
|
289
|
-
self.assertEqual(func_schema["properties"]["b"]["description"], "second name")
|
290
|
-
|
291
|
-
def test_simple_style_docstring_parsing(self):
|
292
|
-
"""
|
293
|
-
Test simple docstring parsing
|
294
|
-
"""
|
295
|
-
|
296
|
-
# given
|
297
|
-
@function_tool
|
298
|
-
def add_numbers(a: int, b: int):
|
299
|
-
"""
|
300
|
-
add two numbers
|
301
|
-
|
302
|
-
a: first name
|
303
|
-
b(int): second name
|
304
|
-
"""
|
305
|
-
return a + b
|
306
|
-
|
307
|
-
# when
|
308
|
-
schema = add_numbers.schema_model()
|
309
|
-
schema_json = schema.model_json_schema()
|
310
|
-
flatten_schema_json = flatten_json_schema(schema_json)
|
311
|
-
func_schema = flatten_schema_json["properties"]["body"]
|
312
|
-
|
313
|
-
# then
|
314
|
-
self.assertTrue(str(func_schema["description"]).startswith("add two numbers"))
|
315
|
-
self.assertEqual(func_schema["title"], "add_numbers")
|
316
|
-
self.assertEqual(func_schema["required"], ["a", "b"])
|
317
|
-
self.assertEqual(func_schema["type"], "object")
|
318
|
-
self.assertEqual(func_schema["properties"]["a"]["type"], "integer")
|
319
|
-
self.assertEqual(func_schema["properties"]["a"]["description"], "first name")
|
320
|
-
self.assertEqual(func_schema["properties"]["b"]["type"], "integer")
|
321
|
-
self.assertEqual(func_schema["properties"]["b"]["description"], "second name")
|
@@ -1,73 +0,0 @@
|
|
1
|
-
import pathlib
|
2
|
-
from tempfile import TemporaryDirectory
|
3
|
-
from unittest import TestCase
|
4
|
-
|
5
|
-
from hyperpocket.repository import Lockfile
|
6
|
-
from hyperpocket.tool import from_local
|
7
|
-
from hyperpocket.tool.wasm import WasmTool
|
8
|
-
from hyperpocket.tool.wasm.invoker import WasmInvoker
|
9
|
-
|
10
|
-
|
11
|
-
class TestWasmTool(TestCase):
|
12
|
-
|
13
|
-
class MockInvoker(WasmInvoker):
|
14
|
-
def invoke(self, tool_path, runtime, body, envs, **kwargs):
|
15
|
-
return str(int(envs["a"]) + int(envs["b"]))
|
16
|
-
async def ainvoke(self, tool_path, runtime, body, envs, **kwargs):
|
17
|
-
return str(int(envs["a"]) + int(envs["b"]))
|
18
|
-
|
19
|
-
def test_wasm_tool_vars_inject(self):
|
20
|
-
with TemporaryDirectory() as tool_dir:
|
21
|
-
with open(f"{tool_dir}/schema.json", "w") as f:
|
22
|
-
f.write("{}")
|
23
|
-
with open(f"{tool_dir}/config.toml", "w") as f:
|
24
|
-
f.write("""
|
25
|
-
name = "mock"
|
26
|
-
description = "mock"
|
27
|
-
language = "python"
|
28
|
-
|
29
|
-
[tool_vars]
|
30
|
-
a = "1"
|
31
|
-
b = "2"
|
32
|
-
""")
|
33
|
-
with open(f"{tool_dir}/README.md", "w") as f:
|
34
|
-
f.write("mock")
|
35
|
-
lockfile = Lockfile(pathlib.Path(tool_dir) / ".lock")
|
36
|
-
req = from_local(tool_dir)
|
37
|
-
lockfile.add_lock(req.lock)
|
38
|
-
lockfile.sync(False)
|
39
|
-
tool = WasmTool.from_tool_request(req, lockfile=lockfile)
|
40
|
-
tool._invoker = self.MockInvoker()
|
41
|
-
tool.override_tool_variables({
|
42
|
-
"a": "1",
|
43
|
-
"b": "1"
|
44
|
-
})
|
45
|
-
self.assertEqual(tool.invoke(body={}, envs={}), "2")
|
46
|
-
|
47
|
-
def test_wasm_tool_vars_inject_with_from_local(self):
|
48
|
-
with TemporaryDirectory() as tool_dir:
|
49
|
-
with open(f"{tool_dir}/schema.json", "w") as f:
|
50
|
-
f.write("{}")
|
51
|
-
with open(f"{tool_dir}/config.toml", "w") as f:
|
52
|
-
f.write("""
|
53
|
-
name = "mock"
|
54
|
-
description = "mock"
|
55
|
-
language = "python"
|
56
|
-
|
57
|
-
[tool_vars]
|
58
|
-
a = "1"
|
59
|
-
b = "2"
|
60
|
-
""")
|
61
|
-
with open(f"{tool_dir}/README.md", "w") as f:
|
62
|
-
f.write("mock")
|
63
|
-
lockfile = Lockfile(pathlib.Path(tool_dir) / ".lock")
|
64
|
-
req = from_local(tool_dir, tool_vars={
|
65
|
-
"a": "1",
|
66
|
-
"b": "1"
|
67
|
-
})
|
68
|
-
lockfile.add_lock(req.lock)
|
69
|
-
lockfile.sync(False)
|
70
|
-
tool = WasmTool.from_tool_request(req, lockfile=lockfile)
|
71
|
-
tool._invoker = self.MockInvoker()
|
72
|
-
self.assertEqual(tool.invoke(body={}, envs={}), "2")
|
73
|
-
|
File without changes
|
File without changes
|