hyperpocket-anthropic 0.1.7__tar.gz → 0.1.9__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,157 @@
1
+ .vs/
2
+ .vscode/
3
+ .idea/
4
+ # Byte-compiled / optimized / DLL files
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+
9
+ # C extensions
10
+ *.so
11
+
12
+ # Distribution / packaging
13
+ .Python
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ pip-wheel-metadata/
27
+ share/python-wheels/
28
+ *.egg-info/
29
+ .installed.cfg
30
+ *.egg
31
+ MANIFEST
32
+
33
+ # Google GitHub Actions credentials files created by:
34
+ # https://github.com/google-github-actions/auth
35
+ #
36
+ # That action recommends adding this gitignore to prevent accidentally committing keys.
37
+ gha-creds-*.json
38
+
39
+ # PyInstaller
40
+ # Usually these files are written by a python script from a template
41
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
42
+ *.manifest
43
+ *.spec
44
+
45
+ # Unit test / coverage reports
46
+ htmlcov/
47
+ .tox/
48
+ .nox/
49
+ .coverage
50
+ .coverage.*
51
+ .cache
52
+ nosetests.xml
53
+ coverage.xml
54
+ *.cover
55
+ *.py,cover
56
+ .hypothesis/
57
+ .pytest_cache/
58
+
59
+ # Translations
60
+ *.mo
61
+ *.pot
62
+
63
+ # Django stuff:
64
+ *.log
65
+ local_settings.py
66
+ db.sqlite3
67
+ db.sqlite3-journal
68
+
69
+ # Flask stuff:
70
+ instance/
71
+ .webassets-cache
72
+
73
+ # Scrapy stuff:
74
+ .scrapy
75
+
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+ notebooks/
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ .python-version
87
+
88
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
89
+ __pypackages__/
90
+
91
+ # Celery stuff
92
+ celerybeat-schedule
93
+ celerybeat.pid
94
+
95
+ # SageMath parsed files
96
+ *.sage.py
97
+
98
+ # Environments
99
+ .env
100
+ .envrc
101
+ .venv*
102
+ venv*
103
+ env/
104
+ ENV/
105
+ env.bak/
106
+
107
+ # Spyder project settings
108
+ .spyderproject
109
+ .spyproject
110
+
111
+ # Rope project settings
112
+ .ropeproject
113
+
114
+ # mkdocs documentation
115
+ /site
116
+
117
+ # mypy
118
+ .mypy_cache/
119
+ .mypy_cache_test/
120
+ .dmypy.json
121
+ dmypy.json
122
+
123
+ # Pyre type checker
124
+ .pyre/
125
+
126
+ # macOS display setting files
127
+ .DS_Store
128
+
129
+ # Wandb directory
130
+ wandb/
131
+
132
+ # asdf tool versions
133
+ .tool-versions
134
+ /.ruff_cache/
135
+
136
+ *.pkl
137
+ *.bin
138
+
139
+ # integration test artifacts
140
+ data_map*
141
+ \[('_type', 'fake'), ('stop', None)]
142
+
143
+ # Replit files
144
+ *replit*
145
+
146
+
147
+ prof
148
+ virtualenv/
149
+
150
+ # Ignore dynaconf secret files
151
+ **/.secrets.toml
152
+ *.xml
153
+ *.iml
154
+ pocket.lock
155
+ .log/
156
+ .pocket/
157
+ _build/
@@ -1,16 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: hyperpocket-anthropic
3
- Version: 0.1.7
4
- Summary:
5
- Author: moon
6
- Author-email: moon@vessl.ai
7
- Requires-Python: >=3.11,<4.0
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.11
10
- Classifier: Programming Language :: Python :: 3.12
11
- Classifier: Programming Language :: Python :: 3.13
12
- Requires-Dist: anthropic (>=0.40.0,<0.41.0)
13
- Requires-Dist: hyperpocket (>=0.0.3,<0.0.4)
3
+ Version: 0.1.9
4
+ Author-email: Hyperpocket Team <hyperpocket@vessl.ai>
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: anthropic>=0.40.0
7
+ Requires-Dist: hyperpocket>=0.0.3
14
8
  Description-Content-Type: text/markdown
15
9
 
16
10
  ## Anthropic extensions
@@ -18,13 +12,15 @@ Description-Content-Type: text/markdown
18
12
  ### Get Pocket Anthropic Tool Spec
19
13
 
20
14
  ```python
21
- import hyperpocket as pk
22
- from pocket_anthropic import PocketAnthropic
15
+ from hyperpocket.tool import from_git
16
+
17
+ from hyperpocket_anthropic import PocketAnthropic
23
18
 
24
19
  pocket = PocketAnthropic(tools=[
25
- *pk.curated_tools.SLACK, # SLACK = [slack_get_message, slack_post_message, ..]
26
- *pk.curated_tools.LINEAR,
27
- "https://github.com/my-org/some-awesome-tool"]
20
+ "https://github.com/my-org/some-awesome-tool",
21
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/get-message"),
22
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/post-message"),
23
+ ]
28
24
  )
29
25
 
30
26
  # get anthropic compatible tool specs from pocket
@@ -90,14 +86,15 @@ response_after_tool_call = llm.messages.create(
90
86
  import os
91
87
 
92
88
  from anthropic import Anthropic
89
+ from hyperpocket.tool import from_git
93
90
 
94
- import hyperpocket as pk
95
- from pocket_anthropic import PocketAnthropic
91
+ from hyperpocket_anthropic import PocketAnthropic
96
92
 
97
93
  pocket = PocketAnthropic(tools=[
98
- *pk.curated_tools.SLACK, # SLACK = [slack_get_message, slack_post_message, ..]
99
- *pk.curated_tools.LINEAR,
100
- "https://github.com/my-org/some-awesome-tool"]
94
+ "https://github.com/my-org/some-awesome-tool",
95
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/get-message"),
96
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/post-message"),
97
+ ]
101
98
  )
102
99
 
103
100
  # get anthropic compatible tool specs from pocket
@@ -141,16 +138,16 @@ response_after_tool_call = llm.messages.create(
141
138
  import os
142
139
 
143
140
  from anthropic import Anthropic
141
+ from hyperpocket.tool import from_git
144
142
 
145
- import hyperpocket as pk
146
- from pocket_anthropic import PocketAnthropic
143
+ from hyperpocket_anthropic import PocketAnthropic
147
144
 
148
145
  client = Anthropic()
149
- pocket = PocketAnthropic(
150
- tools=[
151
- *pk.curated_tools.SLACK, # SLACK = [slack_get_message, slack_post_message, ..]
152
- *pk.curated_tools.LINEAR,
153
- "https://github.com/my-org/some-awesome-tool"]
146
+ pocket = PocketAnthropic(tools=[
147
+ "https://github.com/my-org/some-awesome-tool",
148
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/get-message"),
149
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/post-message"),
150
+ ]
154
151
  )
155
152
 
156
153
  tool_specs = pocket.get_anthropic_tool_specs()
@@ -191,4 +188,4 @@ while True:
191
188
 
192
189
  if response.stop_reason != "tool_use":
193
190
  break
194
- ```
191
+ ```
@@ -3,13 +3,15 @@
3
3
  ### Get Pocket Anthropic Tool Spec
4
4
 
5
5
  ```python
6
- import hyperpocket as pk
7
- from pocket_anthropic import PocketAnthropic
6
+ from hyperpocket.tool import from_git
7
+
8
+ from hyperpocket_anthropic import PocketAnthropic
8
9
 
9
10
  pocket = PocketAnthropic(tools=[
10
- *pk.curated_tools.SLACK, # SLACK = [slack_get_message, slack_post_message, ..]
11
- *pk.curated_tools.LINEAR,
12
- "https://github.com/my-org/some-awesome-tool"]
11
+ "https://github.com/my-org/some-awesome-tool",
12
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/get-message"),
13
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/post-message"),
14
+ ]
13
15
  )
14
16
 
15
17
  # get anthropic compatible tool specs from pocket
@@ -75,14 +77,15 @@ response_after_tool_call = llm.messages.create(
75
77
  import os
76
78
 
77
79
  from anthropic import Anthropic
80
+ from hyperpocket.tool import from_git
78
81
 
79
- import hyperpocket as pk
80
- from pocket_anthropic import PocketAnthropic
82
+ from hyperpocket_anthropic import PocketAnthropic
81
83
 
82
84
  pocket = PocketAnthropic(tools=[
83
- *pk.curated_tools.SLACK, # SLACK = [slack_get_message, slack_post_message, ..]
84
- *pk.curated_tools.LINEAR,
85
- "https://github.com/my-org/some-awesome-tool"]
85
+ "https://github.com/my-org/some-awesome-tool",
86
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/get-message"),
87
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/post-message"),
88
+ ]
86
89
  )
87
90
 
88
91
  # get anthropic compatible tool specs from pocket
@@ -126,16 +129,16 @@ response_after_tool_call = llm.messages.create(
126
129
  import os
127
130
 
128
131
  from anthropic import Anthropic
132
+ from hyperpocket.tool import from_git
129
133
 
130
- import hyperpocket as pk
131
- from pocket_anthropic import PocketAnthropic
134
+ from hyperpocket_anthropic import PocketAnthropic
132
135
 
133
136
  client = Anthropic()
134
- pocket = PocketAnthropic(
135
- tools=[
136
- *pk.curated_tools.SLACK, # SLACK = [slack_get_message, slack_post_message, ..]
137
- *pk.curated_tools.LINEAR,
138
- "https://github.com/my-org/some-awesome-tool"]
137
+ pocket = PocketAnthropic(tools=[
138
+ "https://github.com/my-org/some-awesome-tool",
139
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/get-message"),
140
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/post-message"),
141
+ ]
139
142
  )
140
143
 
141
144
  tool_specs = pocket.get_anthropic_tool_specs()
File without changes
@@ -1,5 +1,7 @@
1
1
  import json
2
- from typing import List
2
+ from typing import List, Optional
3
+
4
+ from pydantic import BaseModel
3
5
 
4
6
  try:
5
7
  from anthropic.types import ToolResultBlockParam, ToolUseBlock
@@ -14,9 +16,27 @@ from hyperpocket_anthropic.util import tool_to_anthropic_spec
14
16
 
15
17
  class PocketAnthropic(Pocket):
16
18
  def invoke(self, tool_use_block: ToolUseBlock, **kwargs) -> ToolResultBlockParam:
17
- body = self.parse_input(tool_use_block)
19
+ if isinstance(tool_use_block.input, str):
20
+ arg = json.loads(tool_use_block.input)
21
+ else:
22
+ arg = tool_use_block.input
23
+
24
+ if self.use_profile:
25
+ body = arg.pop("body")
26
+ thread_id = arg.pop("thread_id", "default")
27
+ profile = arg.pop("profile", "default")
28
+ else:
29
+ body = arg
30
+ thread_id = "default"
31
+ profile = "default"
32
+
33
+ if isinstance(body, BaseModel):
34
+ body = body.model_dump()
35
+ elif isinstance(body, str):
36
+ body = json.loads(body)
37
+
18
38
  result, interrupted = self.invoke_with_state(
19
- tool_use_block.name, body=body, **kwargs
39
+ tool_use_block.name, body=body, thread_id=thread_id, profile=profile, **kwargs
20
40
  )
21
41
  say = result
22
42
  if interrupted:
@@ -33,9 +53,27 @@ class PocketAnthropic(Pocket):
33
53
  async def ainvoke(
34
54
  self, tool_use_block: ToolUseBlock, **kwargs
35
55
  ) -> ToolResultBlockParam:
36
- body = self.parse_input(tool_use_block)
56
+ if isinstance(tool_use_block.input, str):
57
+ arg = json.loads(tool_use_block.input)
58
+ else:
59
+ arg = tool_use_block.input
60
+
61
+ if self.use_profile:
62
+ body = arg.pop("body")
63
+ thread_id = arg.pop("thread_id", "default")
64
+ profile = arg.pop("profile", "default")
65
+ else:
66
+ body = arg
67
+ thread_id = "default"
68
+ profile = "default"
69
+
70
+ if isinstance(body, BaseModel):
71
+ body = body.model_dump()
72
+ elif isinstance(body, str):
73
+ body = json.loads(body)
74
+
37
75
  result, interrupted = await self.ainvoke_with_state(
38
- tool_use_block.name, body=body, **kwargs
76
+ tool_use_block.name, body=body, thread_id=thread_id, profile=profile, **kwargs
39
77
  )
40
78
  say = result
41
79
 
@@ -50,28 +88,16 @@ class PocketAnthropic(Pocket):
50
88
 
51
89
  return tool_result_block
52
90
 
53
- @staticmethod
54
- def parse_input(tool_use_block):
55
- if isinstance(tool_use_block.input, str):
56
- arg = json.loads(tool_use_block.input)
57
- body = arg["body"]
58
- else:
59
- arg = tool_use_block.input
60
- body = arg["body"]
61
-
62
- if isinstance(body, str):
63
- body = json.loads(body)
64
-
65
- return body
91
+ def get_anthropic_tool_specs(self, use_profile: Optional[bool] = None) -> List[dict]:
92
+ if use_profile is not None:
93
+ self.use_profile = use_profile
66
94
 
67
- def get_anthropic_tool_specs(self) -> List[dict]:
68
95
  specs = []
69
- for tool in self.tools.values():
96
+ for tool in self.core.tools.values():
70
97
  spec = self.get_anthropic_tool_spec(tool)
71
98
  specs.append(spec)
72
99
  return specs
73
100
 
74
- @staticmethod
75
- def get_anthropic_tool_spec(tool: Tool) -> dict:
76
- spec = tool_to_anthropic_spec(tool)
101
+ def get_anthropic_tool_spec(self, tool: Tool) -> dict:
102
+ spec = tool_to_anthropic_spec(tool, use_profile=self.use_profile)
77
103
  return spec
@@ -2,10 +2,10 @@ from hyperpocket.tool import Tool
2
2
  from hyperpocket.util.flatten_json_schema import flatten_json_schema
3
3
 
4
4
 
5
- def tool_to_anthropic_spec(tool: Tool) -> dict:
5
+ def tool_to_anthropic_spec(tool: Tool, use_profile: bool) -> dict:
6
6
  name = tool.name
7
- description = tool.description
8
- arg_schema = tool.schema_model()
7
+ description = tool.get_description(use_profile=use_profile)
8
+ arg_schema = tool.schema_model(use_profile=use_profile)
9
9
  json_schema = flatten_json_schema(arg_schema.model_json_schema())
10
10
 
11
11
  anthropic_spec = {
@@ -0,0 +1,18 @@
1
+ [project]
2
+ name = "hyperpocket-anthropic"
3
+ version = "0.1.9"
4
+ description = ""
5
+ authors = [{ name = "Hyperpocket Team", email = "hyperpocket@vessl.ai" }]
6
+ requires-python = ">=3.10"
7
+ readme = "README.md"
8
+ dependencies = ["anthropic>=0.40.0", "hyperpocket>=0.0.3"]
9
+
10
+ [tool.uv.sources]
11
+ hyperpocket = { path = "../../hyperpocket", editable = true }
12
+
13
+ [dependency-groups]
14
+ dev = ["pytest>=8.3.4", "ruff>=0.8.6"]
15
+
16
+ [build-system]
17
+ requires = ["hatchling"]
18
+ build-backend = "hatchling.build"
File without changes
@@ -0,0 +1,148 @@
1
+ import ast
2
+ from unittest.async_case import IsolatedAsyncioTestCase
3
+
4
+ from anthropic import Anthropic
5
+ from pydantic import BaseModel
6
+
7
+ from hyperpocket.config import config, secret
8
+ from hyperpocket.tool import from_git
9
+ from hyperpocket_anthropic import PocketAnthropic
10
+
11
+
12
+ class TestPocketAnthropicNoProfile(IsolatedAsyncioTestCase):
13
+ @staticmethod
14
+ def add(a: int, b: int) -> int:
15
+ """
16
+ Add two numbers
17
+
18
+ Args:
19
+ a(int): first number
20
+ b(int): second number
21
+
22
+ """
23
+
24
+ return a + b
25
+
26
+ class FirstNumber(BaseModel):
27
+ first: int
28
+
29
+ class SecondNumber(BaseModel):
30
+ second: int
31
+
32
+ @staticmethod
33
+ def sub_pydantic_args(a: FirstNumber, b: SecondNumber):
34
+ """
35
+ sub two numbers
36
+
37
+ Args:
38
+ a(FirstNumber): first number
39
+ b(SecondNumber): second number
40
+ """
41
+ return a.first - b.second
42
+
43
+ async def asyncSetUp(self):
44
+ config.public_server_port = "https"
45
+ config.public_hostname = "localhost"
46
+ config.public_server_port = 8001
47
+ config.internal_server_port = 8000
48
+ config.enable_local_callback_proxy = True
49
+
50
+ self.pocket = PocketAnthropic(
51
+ tools=[
52
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main",
53
+ "managed-tools/none/simple-echo-tool"),
54
+ self.add,
55
+ self.sub_pydantic_args
56
+ ],
57
+ )
58
+ self.tool_specs_no_profile = self.pocket.get_anthropic_tool_specs(use_profile=False)
59
+ self.client = Anthropic(api_key=secret["ANTHROPIC_API_KEY"])
60
+
61
+ async def asyncTearDown(self):
62
+ self.pocket._teardown_server()
63
+
64
+ def test_get_tools_from_pocket_no_profile(self):
65
+ # given
66
+ pocket = PocketAnthropic(tools=[
67
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/get-message"),
68
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/post-message"),
69
+ ])
70
+
71
+ # when
72
+ specs = pocket.get_anthropic_tool_specs()
73
+ get_tool, send_tool = specs[0], specs[1]
74
+
75
+ # then
76
+ self.assertIsInstance(get_tool, dict)
77
+ self.assertEqual(get_tool["name"], 'slack_get_messages')
78
+ self.assertTrue("channel" in get_tool["input_schema"]["properties"])
79
+ self.assertTrue("limit" in get_tool["input_schema"]["properties"])
80
+
81
+ self.assertIsInstance(send_tool, dict)
82
+ self.assertEqual(send_tool["name"], 'slack_send_messages')
83
+ self.assertTrue("channel" in send_tool["input_schema"]["properties"])
84
+ self.assertTrue("text" in send_tool["input_schema"]["properties"])
85
+
86
+ async def test_function_tool_no_profile(self):
87
+ response = self.client.messages.create(
88
+ model="claude-3-5-haiku-latest",
89
+ max_tokens=500,
90
+ messages=[{
91
+ "role": "user",
92
+ "content": "add 1, 2"
93
+ }],
94
+ tools=self.tool_specs_no_profile,
95
+ )
96
+
97
+ tool_result_block = None
98
+ for block in response.content:
99
+ if block.type == "tool_use":
100
+ tool_result_block = await self.pocket.ainvoke(block)
101
+
102
+ # then
103
+ self.assertEqual(response.stop_reason, "tool_use")
104
+ self.assertIsNotNone(tool_result_block)
105
+ self.assertEqual(tool_result_block["content"], "3")
106
+
107
+ async def test_pydantic_function_tool_no_profile(self):
108
+ response = self.client.messages.create(
109
+ model="claude-3-5-haiku-latest",
110
+ max_tokens=500,
111
+ messages=[{
112
+ "role": "user",
113
+ "content": "sub 1, 2"
114
+ }],
115
+ tools=self.tool_specs_no_profile,
116
+ )
117
+
118
+ tool_result_block = None
119
+ for block in response.content:
120
+ if block.type == "tool_use":
121
+ tool_result_block = await self.pocket.ainvoke(block)
122
+
123
+ # then
124
+ self.assertEqual(response.stop_reason, "tool_use")
125
+ self.assertIsNotNone(tool_result_block)
126
+ self.assertEqual(tool_result_block["content"], "-1")
127
+
128
+ async def test_wasm_tool_no_profile(self):
129
+ response = self.client.messages.create(
130
+ model="claude-3-5-haiku-latest",
131
+ max_tokens=500,
132
+ messages=[{
133
+ "role": "user",
134
+ "content": "echo 'hello world'"
135
+ }],
136
+ tools=self.tool_specs_no_profile,
137
+ )
138
+
139
+ tool_result_block = None
140
+ for block in response.content:
141
+ if block.type == "tool_use":
142
+ tool_result_block = await self.pocket.ainvoke(block)
143
+ output = ast.literal_eval(tool_result_block["content"])
144
+
145
+ # then
146
+ self.assertEqual(response.stop_reason, "tool_use")
147
+ self.assertIsNotNone(tool_result_block)
148
+ self.assertTrue(output["stdout"].startswith("echo message : hello world"))