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

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperpocket-anthropic
3
- Version: 0.1.8
4
- Author-email: moon <moon@vessl.ai>
3
+ Version: 0.1.9
4
+ Author-email: Hyperpocket Team <hyperpocket@vessl.ai>
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: anthropic>=0.40.0
7
7
  Requires-Dist: hyperpocket>=0.0.3
@@ -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
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 = {
@@ -1,8 +1,8 @@
1
1
  [project]
2
2
  name = "hyperpocket-anthropic"
3
- version = "0.1.8"
3
+ version = "0.1.9"
4
4
  description = ""
5
- authors = [{ name = "moon", email = "moon@vessl.ai" }]
5
+ authors = [{ name = "Hyperpocket Team", email = "hyperpocket@vessl.ai" }]
6
6
  requires-python = ">=3.10"
7
7
  readme = "README.md"
8
8
  dependencies = ["anthropic>=0.40.0", "hyperpocket>=0.0.3"]
@@ -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"))
@@ -1,3 +1,4 @@
1
+ import ast
1
2
  from unittest.async_case import IsolatedAsyncioTestCase
2
3
 
3
4
  from anthropic import Anthropic
@@ -8,7 +9,7 @@ from hyperpocket.tool import from_git
8
9
  from hyperpocket_anthropic import PocketAnthropic
9
10
 
10
11
 
11
- class TestPocketAnthropic(IsolatedAsyncioTestCase):
12
+ class TestPocketAnthropicUseProfile(IsolatedAsyncioTestCase):
12
13
  @staticmethod
13
14
  def add(a: int, b: int) -> int:
14
15
  """
@@ -48,18 +49,19 @@ class TestPocketAnthropic(IsolatedAsyncioTestCase):
48
49
 
49
50
  self.pocket = PocketAnthropic(
50
51
  tools=[
51
- from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/simple-echo-tool"),
52
+ from_git("https://github.com/vessl-ai/hyperawesometools", "main",
53
+ "managed-tools/none/simple-echo-tool"),
52
54
  self.add,
53
55
  self.sub_pydantic_args
54
56
  ],
55
57
  )
56
- self.tool_specs = self.pocket.get_anthropic_tool_specs()
58
+ self.tool_specs_use_profile = self.pocket.get_anthropic_tool_specs(use_profile=True)
57
59
  self.client = Anthropic(api_key=secret["ANTHROPIC_API_KEY"])
58
60
 
59
61
  async def asyncTearDown(self):
60
62
  self.pocket._teardown_server()
61
63
 
62
- def test_get_tools_from_pocket(self):
64
+ def test_get_tools_from_pocket_use_profile(self):
63
65
  # given
64
66
  pocket = PocketAnthropic(tools=[
65
67
  from_git("https://github.com/vessl-ai/hyperawesometools", "main", "managed-tools/slack/get-message"),
@@ -67,7 +69,7 @@ class TestPocketAnthropic(IsolatedAsyncioTestCase):
67
69
  ])
68
70
 
69
71
  # when
70
- specs = pocket.get_anthropic_tool_specs()
72
+ specs = pocket.get_anthropic_tool_specs(use_profile=True)
71
73
  get_tool, send_tool = specs[0], specs[1]
72
74
 
73
75
  # then
@@ -83,7 +85,8 @@ class TestPocketAnthropic(IsolatedAsyncioTestCase):
83
85
  self.assertTrue("channel" in send_tool["input_schema"]["properties"]["body"]["properties"])
84
86
  self.assertTrue("text" in send_tool["input_schema"]["properties"]["body"]["properties"])
85
87
 
86
- async def test_function_tool(self):
88
+
89
+ async def test_function_tool_use_profile(self):
87
90
  response = self.client.messages.create(
88
91
  model="claude-3-5-haiku-latest",
89
92
  max_tokens=500,
@@ -91,7 +94,7 @@ class TestPocketAnthropic(IsolatedAsyncioTestCase):
91
94
  "role": "user",
92
95
  "content": "add 1, 2"
93
96
  }],
94
- tools=self.tool_specs,
97
+ tools=self.tool_specs_use_profile,
95
98
  )
96
99
 
97
100
  tool_result_block = None
@@ -104,7 +107,8 @@ class TestPocketAnthropic(IsolatedAsyncioTestCase):
104
107
  self.assertIsNotNone(tool_result_block)
105
108
  self.assertEqual(tool_result_block["content"], "3")
106
109
 
107
- async def test_pydantic_function_tool(self):
110
+
111
+ async def test_pydantic_function_tool_use_profile(self):
108
112
  response = self.client.messages.create(
109
113
  model="claude-3-5-haiku-latest",
110
114
  max_tokens=500,
@@ -112,7 +116,7 @@ class TestPocketAnthropic(IsolatedAsyncioTestCase):
112
116
  "role": "user",
113
117
  "content": "sub 1, 2"
114
118
  }],
115
- tools=self.tool_specs,
119
+ tools=self.tool_specs_use_profile,
116
120
  )
117
121
 
118
122
  tool_result_block = None
@@ -125,7 +129,8 @@ class TestPocketAnthropic(IsolatedAsyncioTestCase):
125
129
  self.assertIsNotNone(tool_result_block)
126
130
  self.assertEqual(tool_result_block["content"], "-1")
127
131
 
128
- async def test_wasm_tool(self):
132
+
133
+ async def test_wasm_tool_use_profile(self):
129
134
  response = self.client.messages.create(
130
135
  model="claude-3-5-haiku-latest",
131
136
  max_tokens=500,
@@ -133,15 +138,17 @@ class TestPocketAnthropic(IsolatedAsyncioTestCase):
133
138
  "role": "user",
134
139
  "content": "echo 'hello world'"
135
140
  }],
136
- tools=self.tool_specs,
141
+ tools=self.tool_specs_use_profile,
137
142
  )
138
143
 
139
144
  tool_result_block = None
140
145
  for block in response.content:
141
146
  if block.type == "tool_use":
142
147
  tool_result_block = await self.pocket.ainvoke(block)
148
+ output = ast.literal_eval(tool_result_block["content"])
143
149
 
144
150
  # then
145
151
  self.assertEqual(response.stop_reason, "tool_use")
146
152
  self.assertIsNotNone(tool_result_block)
147
- self.assertTrue(tool_result_block["content"].startswith("echo message : hello world"))
153
+ self.assertTrue(output["stdout"].startswith("echo message : hello world"))
154
+