hyperpocket 0.1.8__py3-none-any.whl → 0.1.10__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/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
|