hyperpocket 0.3.6__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. hyperpocket/auth/provider.py +2 -0
  2. hyperpocket/auth/weaviate/context.py +12 -0
  3. hyperpocket/auth/weaviate/token_context.py +11 -0
  4. hyperpocket/auth/weaviate/token_handler.py +68 -0
  5. hyperpocket/auth/weaviate/token_schema.py +7 -0
  6. hyperpocket/auth/zinc/__init__.py +0 -0
  7. hyperpocket/auth/zinc/context.py +12 -0
  8. hyperpocket/auth/zinc/token_context.py +11 -0
  9. hyperpocket/auth/zinc/token_handler.py +64 -0
  10. hyperpocket/auth/zinc/token_schema.py +7 -0
  11. hyperpocket/cli/eject.py +2 -7
  12. hyperpocket/cli/pull.py +2 -7
  13. hyperpocket/config/settings.py +2 -1
  14. hyperpocket/pocket_core.py +41 -68
  15. hyperpocket/pocket_main.py +37 -16
  16. hyperpocket/repository/__init__.py +3 -4
  17. hyperpocket/repository/repository.py +6 -41
  18. hyperpocket/repository/tool_reference.py +28 -0
  19. hyperpocket/server/auth/weaviate.py +27 -0
  20. hyperpocket/server/auth/zinc.py +27 -0
  21. hyperpocket/server/server.py +127 -61
  22. hyperpocket/session/in_memory.py +13 -3
  23. hyperpocket/tool/__init__.py +0 -3
  24. hyperpocket/tool/dock/__init__.py +3 -0
  25. hyperpocket/tool/dock/dock.py +34 -0
  26. hyperpocket/tool/function/__init__.py +1 -1
  27. hyperpocket/tool/function/tool.py +62 -32
  28. hyperpocket/tool/tool.py +1 -9
  29. hyperpocket/tool_like.py +2 -1
  30. hyperpocket/util/generate_slug.py +4 -0
  31. hyperpocket/util/json_schema_to_model.py +5 -1
  32. {hyperpocket-0.3.6.dist-info → hyperpocket-0.4.0.dist-info}/METADATA +4 -1
  33. {hyperpocket-0.3.6.dist-info → hyperpocket-0.4.0.dist-info}/RECORD +36 -36
  34. hyperpocket/cli/sync.py +0 -17
  35. hyperpocket/repository/lock.py +0 -240
  36. hyperpocket/repository/lockfile.py +0 -62
  37. hyperpocket/server/tool/__init__.py +0 -10
  38. hyperpocket/server/tool/dto/script.py +0 -33
  39. hyperpocket/server/tool/wasm.py +0 -46
  40. hyperpocket/tool/wasm/README.md +0 -166
  41. hyperpocket/tool/wasm/__init__.py +0 -3
  42. hyperpocket/tool/wasm/browser.py +0 -63
  43. hyperpocket/tool/wasm/invoker.py +0 -41
  44. hyperpocket/tool/wasm/script.py +0 -134
  45. hyperpocket/tool/wasm/templates/__init__.py +0 -35
  46. hyperpocket/tool/wasm/templates/node.py +0 -87
  47. hyperpocket/tool/wasm/templates/python.py +0 -93
  48. hyperpocket/tool/wasm/tool.py +0 -163
  49. /hyperpocket/{server/tool/dto → auth/weaviate}/__init__.py +0 -0
  50. {hyperpocket-0.3.6.dist-info → hyperpocket-0.4.0.dist-info}/WHEEL +0 -0
  51. {hyperpocket-0.3.6.dist-info → hyperpocket-0.4.0.dist-info}/entry_points.txt +0 -0
@@ -10,7 +10,6 @@ from uvicorn import Config, Server
10
10
  from hyperpocket.config import config, pocket_logger
11
11
  from hyperpocket.pocket_core import PocketCore
12
12
  from hyperpocket.server.auth import auth_router
13
- from hyperpocket.server.tool import tool_router
14
13
 
15
14
 
16
15
  class PocketServerOperations(enum.Enum):
@@ -18,10 +17,12 @@ class PocketServerOperations(enum.Enum):
18
17
  PREPARE_AUTH = "prepare_auth"
19
18
  AUTHENTICATE = "authenticate"
20
19
  TOOL_CALL = "tool_call"
20
+ PLUG_CORE = "plug_core"
21
21
 
22
22
 
23
23
  class PocketServer(object):
24
- main_server: Server
24
+ fastapi_app: Optional[FastAPI]
25
+ main_server: Optional[Server]
25
26
  internal_server_port: int
26
27
  proxy_server: Optional[Server]
27
28
  proxy_port: int
@@ -29,15 +30,64 @@ class PocketServer(object):
29
30
  process: mp.Process
30
31
  future_store: dict[str, asyncio.Future]
31
32
  torn_down: bool = False
32
-
33
- def __init__(
34
- self,
35
- internal_server_port: int = config().internal_server_port,
36
- proxy_port: int = config().public_server_port,
37
- ):
38
- self.internal_server_port = internal_server_port
39
- self.proxy_port = proxy_port
33
+ _uidset: set
34
+ _cores: dict[str, PocketCore]
35
+
36
+ def __init__(self):
37
+ self._initialized = False
38
+ self.internal_server_port = config().internal_server_port
39
+ self.proxy_port = config().public_server_port
40
+ self._uidset = set()
40
41
  self.future_store = dict()
42
+ self.fastapi_app = None
43
+ self.main_server = None
44
+ self._cores = dict()
45
+
46
+ # should be called in child process
47
+ def _plug_core(self, pocket_uid: str, pocket_core: PocketCore, *_a, **_kw):
48
+ # extend http routers from each docks
49
+ dock_routes = set([str(r) for r in self.fastapi_app.routes])
50
+
51
+ for dock in pocket_core.docks:
52
+ # check duplicated api route
53
+ dock_route = set([str(r) for r in dock.router.routes])
54
+ if dock_route in dock_routes:
55
+ continue
56
+
57
+ dock_routes.update(dock_route)
58
+ self.fastapi_app.include_router(dock.router)
59
+
60
+ # keep pocket core
61
+ self._cores[pocket_uid] = pocket_core
62
+
63
+ # should be called in parent process
64
+ async def plug_core(self, pocket_uid: str, pocket_core: PocketCore):
65
+ await self.call_in_subprocess(
66
+ PocketServerOperations.PLUG_CORE,
67
+ pocket_uid,
68
+ tuple(),
69
+ {
70
+ "pocket_uid": pocket_uid,
71
+ "pocket_core": pocket_core,
72
+ },
73
+ )
74
+
75
+ @classmethod
76
+ def get_instance_and_refcnt_up(cls, uid: str):
77
+ if cls.__dict__.get("_instance") is None:
78
+ cls._instance = cls()
79
+ cls._instance.run()
80
+ cls._instance.refcnt_up(uid)
81
+ return cls._instance
82
+
83
+ def refcnt_up(self, uid: str):
84
+ self._uidset.add(uid)
85
+
86
+ def refcnt_down(self, uid: str):
87
+ if uid in self._uidset:
88
+ self._uidset.remove(uid)
89
+ if len(self._uidset) == 0:
90
+ self._instance.teardown()
41
91
 
42
92
  def teardown(self):
43
93
  # @XXX(seokju) is it ok to call this method both in __del__ and __exit__?
@@ -63,20 +113,21 @@ class PocketServer(object):
63
113
  loop = asyncio.get_running_loop()
64
114
  _, conn = self.pipe
65
115
 
66
- async def _acall(_conn, _op, _uid, a, kw):
116
+ async def _acall(_conn, _future_uid, _core_uid, _args, _kwargs):
67
117
  try:
68
- result = await self.pocket_core.acall(*a, **kw)
118
+ core = self._cores[_core_uid]
119
+ result = await core.acall(*_args, **_kwargs)
69
120
  error = None
70
121
  except Exception as e:
71
- pocket_logger.error(f"failed to acall in pocket subprocess. error: {e}")
122
+ pocket_logger.error(f"failed in pocket subprocess. error: {e}")
72
123
  result = None
73
124
  error = e
125
+ _conn.send((_future_uid, result, error))
74
126
 
75
- _conn.send((_op, _uid, result, error))
76
-
77
- async def _prepare(_conn, _op, _uid, a, kw):
127
+ async def _prepare(_conn, _future_uid, _core_uid, a, kw):
78
128
  try:
79
- result = self.pocket_core.prepare_auth(*a, **kw)
129
+ core = self._cores[_core_uid]
130
+ result = core.prepare_auth(*a, **kw)
80
131
  error = None
81
132
  except Exception as e:
82
133
  pocket_logger.error(
@@ -85,11 +136,12 @@ class PocketServer(object):
85
136
  result = None
86
137
  error = e
87
138
 
88
- _conn.send((_op, _uid, result, error))
139
+ _conn.send((_future_uid, result, error))
89
140
 
90
- async def _authenticate(_conn, _op, _uid, a, kw):
141
+ async def _authenticate(_conn, _future_uid, _core_uid, a, kw):
91
142
  try:
92
- result = await self.pocket_core.authenticate(*a, **kw)
143
+ core = self._cores[_core_uid]
144
+ result = await core.authenticate(*a, **kw)
93
145
  error = None
94
146
  except Exception as e:
95
147
  pocket_logger.error(
@@ -98,11 +150,12 @@ class PocketServer(object):
98
150
  result = None
99
151
  error = e
100
152
 
101
- _conn.send((_op, _uid, result, error))
153
+ _conn.send((_future_uid, result, error))
102
154
 
103
- async def _tool_call(_conn, _op, _uid, a, kw):
155
+ async def _tool_call(_conn, _future_uid, _core_uid, a, kw):
104
156
  try:
105
- result = await self.pocket_core.tool_call(*a, **kw)
157
+ core = self._cores[_core_uid]
158
+ result = await core.tool_call(*a, **kw)
106
159
  error = None
107
160
  except Exception as e:
108
161
  pocket_logger.error(
@@ -111,38 +164,50 @@ class PocketServer(object):
111
164
  result = None
112
165
  error = e
113
166
 
114
- _conn.send((_op, _uid, result, error))
167
+ _conn.send((_future_uid, result, error))
168
+
169
+ async def _plug_core(_conn, _future_uid, _core_uid, a, kw):
170
+ try:
171
+ self._plug_core(*a, **kw)
172
+ _conn.send((_future_uid, None, None))
173
+ except Exception as e:
174
+ pocket_logger.error(
175
+ f"failed to plug_core in pocket subprocess. error: {e}"
176
+ )
177
+ _conn.send((_future_uid, None, e))
115
178
 
116
179
  while True:
117
180
  if conn.poll():
118
- op, uid, args, kwargs = conn.recv()
119
- if op == PocketServerOperations.CALL.value:
120
- loop.create_task(_acall(conn, op, uid, args, kwargs))
121
- elif op == PocketServerOperations.PREPARE_AUTH.value:
122
- loop.create_task(_prepare(conn, op, uid, args, kwargs))
123
- elif op == PocketServerOperations.AUTHENTICATE.value:
124
- loop.create_task(_authenticate(conn, op, uid, args, kwargs))
125
- elif op == PocketServerOperations.TOOL_CALL.value:
126
- loop.create_task(_tool_call(conn, op, uid, args, kwargs))
181
+ op, future_uid, core_uid, args, kwargs = conn.recv()
182
+ if op == PocketServerOperations.CALL:
183
+ loop.create_task(_acall(conn, future_uid, core_uid, args, kwargs))
184
+ elif op == PocketServerOperations.PREPARE_AUTH:
185
+ loop.create_task(_prepare(conn, future_uid, core_uid, args, kwargs))
186
+ elif op == PocketServerOperations.AUTHENTICATE:
187
+ loop.create_task(_authenticate(conn, future_uid, core_uid, args, kwargs))
188
+ elif op == PocketServerOperations.TOOL_CALL:
189
+ loop.create_task(_tool_call(conn, future_uid, core_uid, args, kwargs))
190
+ elif op == PocketServerOperations.PLUG_CORE:
191
+ loop.create_task(_plug_core(conn, future_uid, core_uid, args, kwargs))
127
192
  else:
128
- raise AttributeError(f"Can't find operations. op:{op}")
193
+ raise ValueError(f"Unknown operation: {op}")
129
194
  else:
130
195
  await asyncio.sleep(0)
131
196
 
132
- def send_in_parent(self, op: PocketServerOperations, args: tuple, kwargs: dict):
197
+ def send_in_parent(self, op: PocketServerOperations, pocket_uid: str, args: tuple, kwargs: dict):
133
198
  conn, _ = self.pipe
134
- uid = str(uuid.uuid4())
135
- message = (op.value, uid, args, kwargs)
199
+ future_uid = str(uuid.uuid4())
200
+ message = (op, future_uid, pocket_uid, args, kwargs)
136
201
  future = asyncio.Future()
137
- self.future_store[uid] = future
202
+ self.future_store[future_uid] = future
138
203
  conn.send(message)
139
- return uid
204
+ return future_uid
140
205
 
141
206
  async def poll_in_parent(self):
142
207
  conn, _ = self.pipe
143
208
  while True:
144
209
  if conn.poll():
145
- op, uid, result, error = conn.recv()
210
+ uid, result, error = conn.recv()
146
211
  future = self.future_store[uid]
147
212
  if error:
148
213
  future.set_exception(error)
@@ -153,19 +218,21 @@ class PocketServer(object):
153
218
  await asyncio.sleep(0)
154
219
 
155
220
  async def call_in_subprocess(
156
- self, op: PocketServerOperations, args: tuple, kwargs: dict
221
+ self, op: PocketServerOperations, pocket_uid: str, args: tuple, kwargs: dict
157
222
  ):
158
- uid = self.send_in_parent(op, args, kwargs)
223
+ uid = self.send_in_parent(op, pocket_uid, args, kwargs)
159
224
  loop = asyncio.get_running_loop()
160
225
  loop.create_task(self.poll_in_parent())
161
226
  return await self.future_store[uid]
162
227
 
163
- def run(self, pocket_core: PocketCore):
228
+ def run(self):
164
229
  self._set_mp_start_method()
165
230
 
166
231
  error_queue = mp.Queue()
167
232
  self.pipe = mp.Pipe()
168
- self.process = mp.Process(target=self._run, args=(pocket_core,))
233
+ self.process = mp.Process(
234
+ target=self._run
235
+ )
169
236
  self.process.start() # process start
170
237
 
171
238
  if not error_queue.empty():
@@ -174,27 +241,25 @@ class PocketServer(object):
174
241
 
175
242
  def _report_initialized(self, error: Optional[Exception] = None):
176
243
  _, conn = self.pipe
177
- conn.send(
178
- (
179
- "server-initialization",
180
- error,
181
- )
182
- )
244
+ conn.send(('server-initialization', error,))
183
245
 
184
246
  def wait_initialized(self):
247
+ if self._initialized:
248
+ return
185
249
  conn, _ = self.pipe
186
250
  while True:
187
251
  if conn.poll():
188
252
  _, error = conn.recv()
189
253
  if error:
190
254
  raise error
191
- return
255
+ break
256
+ self._initialized = True
192
257
 
193
- def _run(self, pocket_core):
258
+ def _run(self):
194
259
  try:
195
260
  # init process
196
- self.pocket_core = pocket_core
197
- self.main_server = self._create_main_server()
261
+ self.fastapi_app = self._create_fastapi_app()
262
+ self.main_server = self._create_main_server(self.fastapi_app)
198
263
  self.proxy_server = self._create_https_proxy_server()
199
264
  self._report_initialized()
200
265
 
@@ -204,20 +269,21 @@ class PocketServer(object):
204
269
  except Exception as error:
205
270
  self._report_initialized(error)
206
271
 
207
- def _create_main_server(self) -> Server:
272
+ def _create_fastapi_app(self) -> FastAPI:
208
273
  app = FastAPI()
274
+ app.include_router(auth_router)
275
+ app.add_api_route("/health", lambda: {"status": "ok"}, methods=["GET"])
276
+ return app
277
+
278
+ def _create_main_server(self, app: FastAPI) -> Server:
209
279
  _config = Config(
210
280
  app,
211
281
  host="0.0.0.0",
212
282
  port=self.internal_server_port,
213
283
  log_level=config().log_level,
214
284
  )
215
- app.include_router(tool_router)
216
- app.include_router(auth_router)
217
- app.add_api_route("/health", lambda: {"status": "ok"}, methods=["GET"])
218
-
219
- app = Server(_config)
220
- return app
285
+ server = Server(_config)
286
+ return server
221
287
 
222
288
  def _create_https_proxy_server(self) -> Optional[Server]:
223
289
  if not config().enable_local_callback_proxy:
@@ -19,10 +19,20 @@ InMemorySessionValue = BaseSessionValue
19
19
  class InMemorySessionStorage(
20
20
  SessionStorageInterface[InMemorySessionKey, InMemorySessionValue]
21
21
  ):
22
- # TODO(moon) : Force it to always take SessionConfig as an input
22
+ _instance = None
23
+ _is_initialized = False
24
+
25
+ def __new__(cls, *args, **kwargs):
26
+ if cls._instance is None:
27
+ cls._instance = super(InMemorySessionStorage, cls).__new__(cls)
28
+ cls._instance._is_initialized = False
29
+ return cls._instance
30
+
23
31
  def __init__(self, session_config: SessionConfigInMemory):
24
- super().__init__()
25
- self.storage: Dict[InMemorySessionKey, InMemorySessionValue] = {}
32
+ if not self._is_initialized:
33
+ super().__init__()
34
+ self.storage: Dict[InMemorySessionKey, InMemorySessionValue] = {}
35
+ self._is_initialized = True
26
36
 
27
37
  @classmethod
28
38
  def session_storage_type(cls) -> SessionType:
@@ -1,13 +1,10 @@
1
1
  from hyperpocket.tool.function import from_dock, from_func, function_tool
2
2
  from hyperpocket.tool.tool import Tool, ToolAuth, ToolRequest
3
- from hyperpocket.tool.wasm.tool import from_git, from_local
4
3
 
5
4
  __all__ = [
6
5
  "Tool",
7
6
  "ToolAuth",
8
7
  "ToolRequest",
9
- "from_local",
10
- "from_git",
11
8
  "from_dock",
12
9
  "from_func",
13
10
  "function_tool",
@@ -0,0 +1,3 @@
1
+ from hyperpocket.tool.dock.dock import Dock
2
+
3
+ __all__ = ["Dock"]
@@ -0,0 +1,34 @@
1
+ import abc
2
+ from typing import Any
3
+
4
+ from fastapi import APIRouter
5
+
6
+ from hyperpocket.tool import ToolRequest
7
+ from hyperpocket.tool.function import FunctionTool
8
+
9
+
10
+ class Dock(abc.ABC):
11
+ _tool_requests: list[ToolRequest]
12
+ _dock_http_router: APIRouter
13
+ _dock_vars: dict[str, str]
14
+
15
+ def __init__(self, dock_vars: dict[str, str] = None):
16
+ self._dock_http_router = APIRouter()
17
+ self._tool_requests = []
18
+ self._dock_vars = dock_vars if dock_vars is not None else {}
19
+
20
+ @property
21
+ def router(self):
22
+ return self._dock_http_router
23
+
24
+ @abc.abstractmethod
25
+ def plug(self, req_like: Any, **kwargs):
26
+ raise NotImplementedError
27
+
28
+ @abc.abstractmethod
29
+ def tools(self) -> list[FunctionTool]:
30
+ raise NotImplementedError
31
+
32
+ @abc.abstractmethod
33
+ async def teardown(self):
34
+ raise NotImplementedError
@@ -4,4 +4,4 @@ from hyperpocket.tool.function.tool import FunctionTool
4
4
  from_func = FunctionTool.from_func
5
5
  from_dock = FunctionTool.from_dock
6
6
 
7
- __all__ = ["from_func", "from_dock", "function_tool"]
7
+ __all__ = ["from_func", "from_dock", "function_tool", "FunctionTool"]
@@ -17,19 +17,36 @@ class FunctionTool(Tool):
17
17
 
18
18
  func: Optional[Callable[..., str]]
19
19
  afunc: Optional[Callable[..., Coroutine[Any, Any, str]]]
20
+ keep_structured_arguments: bool = False
20
21
 
21
22
  def invoke(self, **kwargs) -> str:
22
23
  binding_args = self._get_binding_args(kwargs)
23
24
  if self.func is None:
24
25
  if self.afunc is None:
25
26
  raise ValueError("Both func and afunc are None")
27
+ try:
28
+ loop = asyncio.get_running_loop()
29
+ return str(loop.run_until_complete(self.afunc(**binding_args)))
30
+ except RuntimeError:
31
+ pass
32
+ except Exception as e:
33
+ import traceback
34
+ traceback.print_exc()
35
+ traceback.print_stack()
36
+ return "There was an error while executing the tool: " + str(e)
26
37
  try:
27
38
  return str(asyncio.run(self.afunc(**binding_args)))
28
39
  except Exception as e:
40
+ import traceback
41
+ traceback.print_exc()
42
+ traceback.print_stack()
29
43
  return "There was an error while executing the tool: " + str(e)
30
44
  try:
31
45
  return str(self.func(**binding_args))
32
46
  except Exception as e:
47
+ import traceback
48
+ traceback.print_exc()
49
+ traceback.print_stack()
33
50
  return "There was an error while executing the tool: " + str(e)
34
51
 
35
52
  async def ainvoke(self, **kwargs) -> str:
@@ -39,9 +56,16 @@ class FunctionTool(Tool):
39
56
  binding_args = self._get_binding_args(kwargs)
40
57
  return str(await self.afunc(**binding_args))
41
58
  except Exception as e:
59
+ import traceback
60
+ traceback.print_exc()
61
+ traceback.print_stack()
42
62
  return "There was an error while executing the tool: " + str(e)
43
63
 
44
64
  def _get_binding_args(self, kwargs):
65
+ if self.keep_structured_arguments:
66
+ if kwargs.get("envs") is not None:
67
+ kwargs["envs"] |= self.tool_vars
68
+ return kwargs
45
69
  _kwargs = copy.deepcopy(kwargs)
46
70
 
47
71
  # make body args to model
@@ -54,28 +78,23 @@ class FunctionTool(Tool):
54
78
 
55
79
  # binding args
56
80
  binding_args = {}
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
81
+ sig = inspect.signature(self.func)
82
+ for param_name, param in sig.parameters.items():
83
+ if param_name not in args:
84
+ continue
66
85
 
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
86
+ if param.kind == param.VAR_KEYWORD:
87
+ # var keyword args should be passed by plain dict
88
+ binding_args |= args[param_name]
89
+ binding_args |= _kwargs.get("envs", {}) | self.tool_vars
71
90
 
72
- if "envs" in _kwargs:
73
- _kwargs.pop("envs")
91
+ if "envs" in _kwargs:
92
+ _kwargs.pop("envs")
74
93
 
75
- binding_args |= _kwargs # add other kwargs
76
- continue
94
+ binding_args |= _kwargs # add other kwargs
95
+ continue
77
96
 
78
- binding_args[param_name] = args[param_name]
97
+ binding_args[param_name] = args[param_name]
79
98
 
80
99
  return binding_args
81
100
 
@@ -89,11 +108,15 @@ class FunctionTool(Tool):
89
108
 
90
109
  @classmethod
91
110
  def from_func(
92
- cls,
93
- func: Callable | "FunctionTool",
94
- afunc: Callable[..., Coroutine[Any, Any, str]] | "FunctionTool" = None,
95
- auth: Optional[ToolAuth] = None,
96
- tool_vars: dict[str, str] = None,
111
+ cls,
112
+ func: Callable | "FunctionTool",
113
+ afunc: Callable[..., Coroutine[Any, Any, str]] | "FunctionTool" = None,
114
+ name: str = None,
115
+ description: str = None,
116
+ json_schema: dict[str, Any] = None,
117
+ auth: Optional[ToolAuth] = None,
118
+ tool_vars: dict[str, str] = None,
119
+ keep_structured_arguments: bool = False,
97
120
  ) -> "FunctionTool":
98
121
  if tool_vars is None:
99
122
  tool_vars = dict()
@@ -109,28 +132,35 @@ class FunctionTool(Tool):
109
132
  elif not callable(func) and not callable(afunc):
110
133
  raise ValueError("FunctionTool can only be created from a callable")
111
134
 
112
- model = function_to_model(func)
113
- argument_json_schema = flatten_json_schema(model.model_json_schema())
135
+ name = name or (func and func.__name__) or (afunc and afunc.__name__)
136
+ description = description or (func and func.__doc__) or (afunc and func.__doc__)
137
+ schema = json_schema or \
138
+ (func and function_to_model(func).model_json_schema()) or \
139
+ (afunc and function_to_model(afunc).model_json_schema())
140
+ argument_json_schema = flatten_json_schema(schema)
114
141
 
115
142
  return cls(
116
143
  func=func,
117
144
  afunc=afunc,
118
- name=func.__name__,
119
- description=func.__doc__ if func.__doc__ is not None else "",
145
+ name=name,
146
+ description=description,
120
147
  argument_json_schema=argument_json_schema,
121
148
  auth=auth,
122
149
  default_tool_vars=tool_vars,
150
+ keep_structured_arguments=keep_structured_arguments,
123
151
  )
124
152
 
125
153
  @classmethod
126
154
  def from_dock(
127
- cls,
128
- dock: list[Callable[..., str]],
129
- tool_vars: Optional[dict[str, str]] = None,
155
+ cls,
156
+ dock: list[Callable[..., str]],
157
+ tool_vars: Optional[dict[str, str]] = None,
130
158
  ) -> list["FunctionTool"]:
131
159
  if tool_vars is None:
132
160
  tool_vars = dict()
133
161
  tools = []
162
+
163
+ # @moon: will be refactored.
134
164
  for func in dock:
135
165
  if (_model := func.__dict__.get("__model__")) is not None:
136
166
  model = _model
@@ -155,7 +185,7 @@ class FunctionTool(Tool):
155
185
  argument_json_schema=argument_json_schema,
156
186
  auth=auth,
157
187
  default_tool_vars=(
158
- tool_vars | func.__dict__.get("__vars__", {})
188
+ tool_vars | func.__dict__.get("__vars__", {})
159
189
  ),
160
190
  )
161
191
  )
@@ -169,7 +199,7 @@ class FunctionTool(Tool):
169
199
  argument_json_schema=argument_json_schema,
170
200
  auth=auth,
171
201
  default_tool_vars=(
172
- tool_vars | func.__dict__.get("__vars__", {})
202
+ tool_vars | func.__dict__.get("__vars__", {})
173
203
  ),
174
204
  )
175
205
  )
hyperpocket/tool/tool.py CHANGED
@@ -116,21 +116,13 @@ class Tool(BaseModel, abc.ABC):
116
116
  return self.description
117
117
 
118
118
  def override_tool_variables(self, override_vars: dict[str, str]) -> "Tool":
119
- self.overridden_tool_vars = override_vars
119
+ self.overridden_tool_vars |= override_vars
120
120
  return self
121
121
 
122
122
  @property
123
123
  def tool_vars(self) -> dict[str, str]:
124
124
  return self.default_tool_vars | self.overridden_tool_vars
125
125
 
126
- @classmethod
127
- def from_tool_request(cls, tool_req: ToolRequest, **kwargs) -> "Tool":
128
- from hyperpocket.tool.wasm.tool import WasmTool, WasmToolRequest
129
-
130
- if isinstance(tool_req, WasmToolRequest):
131
- return WasmTool.from_tool_request(tool_req, **kwargs)
132
- raise ValueError("Unknown tool request type")
133
-
134
126
  @classmethod
135
127
  def _get_schema_model(
136
128
  cls, name: str, json_schema: Optional[dict], use_profile: bool
hyperpocket/tool_like.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from typing import Callable, Union
2
2
 
3
3
  from hyperpocket.tool import Tool, ToolRequest
4
+ from hyperpocket.tool.dock import Dock
4
5
 
5
- ToolLike = Union[Tool, str, Callable, ToolRequest]
6
+ ToolLike = Union[Tool, str, tuple, Callable, ToolRequest, Dock]
@@ -0,0 +1,4 @@
1
+ import string, random
2
+
3
+ def generate_slug(length: int = 6) -> str:
4
+ return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
@@ -60,13 +60,17 @@ def json_schema_to_model(
60
60
  fields["additional_properties"] = (dict[str, additional_model], {})
61
61
 
62
62
  # Create the model
63
- model = create_model(model_name, **fields)
63
+ model = create_model(f"{model_name}", **fields)
64
64
 
65
65
  # Add custom Config class to handle extra properties
66
66
  class Config:
67
67
  extra = config_extra
68
68
 
69
69
  model.Config = Config
70
+
71
+ # workaround for pickling dynamic classes
72
+ model.__module__ = "__main__"
73
+ model.__qualname__ = model.__name__.split('.')[-1]
70
74
 
71
75
  return model
72
76
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperpocket
3
- Version: 0.3.6
3
+ Version: 0.4.0
4
4
  Summary: Building AI agent with hyperpocket tool in a flash
5
5
  Project-URL: Homepage, https://vessl-ai.github.io/hyperpocket
6
6
  Project-URL: Repository, https://github.com/vessl-ai/hyperpocket
@@ -13,6 +13,7 @@ Requires-Dist: gitpython>=3.1.43
13
13
  Requires-Dist: httpx==0.27
14
14
  Requires-Dist: jinja2>=3.1.4
15
15
  Requires-Dist: multiprocess>=0.70.17
16
+ Requires-Dist: nest-asyncio>=1.6.0
16
17
  Requires-Dist: playwright>=1.49.0
17
18
  Requires-Dist: pydantic>=2.10.2
18
19
  Requires-Dist: pygithub>=2.5.0
@@ -21,6 +22,8 @@ Requires-Dist: redis>=5.2.1
21
22
  Requires-Dist: requests>=2.32.3
22
23
  Requires-Dist: toml>=0.10.2
23
24
  Requires-Dist: uvicorn>=0.32.1
25
+ Provides-Extra: standard
26
+ Requires-Dist: hyperdock-container; extra == 'standard'
24
27
  Description-Content-Type: text/markdown
25
28
 
26
29
  <p align="center">