hyperpocket 0.1.8__py3-none-any.whl → 0.1.10__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
-