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.
@@ -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
- pocket_root = Path.home() / ".pocket"
11
- if not pocket_root.exists():
12
- os.makedirs(pocket_root)
13
- settings_path = pocket_root / "settings.toml"
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
- secret_path = pocket_root / ".secrets.toml"
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
- toolpkg_path = pocket_root / "toolpkg"
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 == 'https' and self.public_server_port == 443:
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 == 'http' and self.public_server_port == 80:
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
 
@@ -38,8 +38,10 @@ class PocketCore:
38
38
  lock = LocalLock(tool_like)
39
39
  req = WasmToolRequest(lock, "")
40
40
  else:
41
- lock = GitLock(repository_url=tool_like, git_ref='HEAD')
42
- req = WasmToolRequest(lock, "")
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):
@@ -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,
@@ -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
@@ -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
- future.set_result(result)
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,), daemon=True)
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 pocket_root
189
- ssl_keypath = pocket_root / "callback_server.key"
190
- ssl_certpath = pocket_root / "callback_server.crt"
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(body=_kwargs["body"])
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.body)
53
+ args = self.model_to_kwargs(model)
56
54
 
57
55
  # binding args
58
56
  binding_args = {}
59
- sig = inspect.signature(self.func)
60
- for param_name, param in sig.parameters.items():
61
- if param_name not in args:
62
- continue
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
- if param.kind == param.VAR_KEYWORD:
65
- # var keyword args should be passed by plain dict
66
- binding_args |= args[param_name]
67
- binding_args |= _kwargs.get("envs", {}) | self.tool_vars
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
- if "envs" in _kwargs:
70
- _kwargs.pop("envs")
72
+ if "envs" in _kwargs:
73
+ _kwargs.pop("envs")
71
74
 
72
- binding_args |= _kwargs # add other kwargs
73
- continue
75
+ binding_args |= _kwargs # add other kwargs
76
+ continue
74
77
 
75
- binding_args[param_name] = args[param_name]
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=model.__doc__,
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
- model = function_to_model(func)
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, description="postprocessing functions after tool is invoked")
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
- extended_schema = {
118
- 'title': name,
119
- 'type': 'object',
120
- 'properties': {
121
- 'thread_id': {
122
- 'type': 'string',
123
- 'default': 'default',
124
- 'description': 'The ID of the chat thread where the tool is invoked. Omitted when unknown.',
125
- },
126
- 'profile': {
127
- 'type': 'string',
128
- 'default': 'default',
129
- 'description': '''The profile of the user invoking the tool. Inferred from user's messages.
130
- Users can request tools to be invoked in specific personas, which is called a profile.
131
- If the user's profile name can be inferred from the query, pass it as a string in the 'profile'
132
- JSON property. Omitted when unknown.''',
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
- 'body': json_schema
135
- },
136
- 'required': [
137
- 'body',
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}")
@@ -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=is_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 = clean_bracket_content(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)\s+(\w+)(?:\s*:\s*|\s+|:\s+)(.*)"
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"^\s*(\w+)\s*\(\s*(.*?)\s*\)\s*:\s*(.*)", line)
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.8
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: VESSL AI Devs <dev@vessl.ai>
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
- # Pocket 👛
26
+ # Hyperpocket 👛
27
27
 
28
- Pocket is where tools belong. Power your agent up with a pocket of tools. 👛
28
+ Hyperpocket is where tools belong. Power your agent up with a pocket of tools. 👛
29
29
 
30
30
  <figure>
31
- <img src="image.png" alt="4d-pocket" width="200"/>
32
- <figcaption>© Doraemon</figcaption>
31
+ <img src="../../logo.png" alt="hyperpocket" width="200"/>
33
32
  </figure>
34
33
 
35
34
  ## Introduction
36
35
 
37
- Pocket is a tool that allows you to easily use tool and auth for agents on your machine.
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 Pocket and use it. We know you don't have time to authenticate to our server.
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
- from hyperpocket.tool import from_git
79
+
86
80
  from langchain_openai import ChatOpenAI
87
81
 
88
82
  from hyperpocket_langchain import PocketLangchain
89
83
 
90
84
  pklc = PocketLangchain(
91
- tools=[
92
- from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/get-message"),
93
- from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/post-message"),
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
- Pocket provides way to use end user auth easily.
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
- from hyperpocket.tool import from_git
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
- tools=[
142
- from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/get-message"),
143
- from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/post-message"),
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
- tools=[
179
- from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/get-message"),
180
- from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/post-message"),
181
- from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/linear/get-issues"),
182
- from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/google/get-calendar-events"),
183
- from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/google/get-calendar-list"),
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 pocket dev server is `8000`. If you are using a different port, make sure to
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=xyma8ucYVYID2gSnc5h5nF_nNMFFtimE-8la1IW1RZA,10035
6
- hyperpocket/pocket_main.py,sha256=UPdRS7ydQAmkqyGUj6ANf4lIr_oKXw-_71k37XCsOa4,10148
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=tKOFZdOk4pFa7RrPBTY7-NE7Bo_bBZS1Eeez03SedAU,2042
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=nLuiszT3oECJJxVmTDdBARqv4x25DsKuL4253gJEJfI,6075
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=yktdzmh5vZj9y1n8i1vBHuNV5TAXeJBpKovO_RkAax0,7810
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=uP22MWN50nauFD9xkzc3jjGRO-nhxkYBaO1FVKiWu-Q,6778
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=E9xND4Kv9nHGDGZEQTnQi9qEUmsnkwvl9J-k2LOJFKI,5953
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=X2aw9sZy306dKDR0knqn4fGC6O1fw08M9t_zIkjUeWs,2083
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=3bek0BRwDGdlKBATDAhBrSSqzdVshXKD02zNi6KvxO4,4067
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=fmIdIAkWfh7qLLoUrK4gqBV1JCHGQgjiEA8rwbpqoMg,3406
136
- hyperpocket-0.1.8.dist-info/METADATA,sha256=azLitNUYizbwI_VGTUmY2KvbQjtt-b6uaCkGeXq61dE,10170
137
- hyperpocket-0.1.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
138
- hyperpocket-0.1.8.dist-info/entry_points.txt,sha256=KpBleaYr0SaENXOa-dFvJ_cvFCHYFEQ4LMl11ShAcBI,61
139
- hyperpocket-0.1.8.dist-info/RECORD,,
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
-