payi 0.1.0a40__py3-none-any.whl → 0.1.0a42__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.

Potentially problematic release.


This version of payi might be problematic. Click here for more details.

payi/_constants.py CHANGED
@@ -6,7 +6,7 @@ RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response"
6
6
  OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"
7
7
 
8
8
  # default timeout is 1 minute
9
- DEFAULT_TIMEOUT = httpx.Timeout(timeout=60.0, connect=5.0)
9
+ DEFAULT_TIMEOUT = httpx.Timeout(timeout=60, connect=5.0)
10
10
  DEFAULT_MAX_RETRIES = 2
11
11
  DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=100, max_keepalive_connections=20)
12
12
 
payi/_models.py CHANGED
@@ -172,7 +172,7 @@ class BaseModel(pydantic.BaseModel):
172
172
  @override
173
173
  def __str__(self) -> str:
174
174
  # mypy complains about an invalid self arg
175
- return f'{self.__repr_name__()}({self.__repr_str__(", ")})' # type: ignore[misc]
175
+ return f"{self.__repr_name__()}({self.__repr_str__(', ')})" # type: ignore[misc]
176
176
 
177
177
  # Override the 'construct' method in a way that supports recursive parsing without validation.
178
178
  # Based on https://github.com/samuelcolvin/pydantic/issues/1168#issuecomment-817742836.
payi/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2
2
 
3
3
  __title__ = "payi"
4
- __version__ = "0.1.0-alpha.40" # x-release-please-version
4
+ __version__ = "0.1.0-alpha.42" # x-release-please-version
@@ -7,7 +7,7 @@ from wrapt import wrap_function_wrapper # type: ignore
7
7
  from payi.types import IngestUnitsParams
8
8
  from payi.types.ingest_units_params import Units
9
9
 
10
- from .instrument import PayiInstrumentor
10
+ from .instrument import IsStreaming, PayiInstrumentor
11
11
 
12
12
 
13
13
  class AnthropicIntrumentor:
@@ -16,12 +16,6 @@ class AnthropicIntrumentor:
16
16
  try:
17
17
  import anthropic # type: ignore # noqa: F401 I001
18
18
 
19
- # wrap_function_wrapper(
20
- # "anthropic.resources.completions",
21
- # "Completions.create",
22
- # chat_wrapper(instrumentor),
23
- # )
24
-
25
19
  wrap_function_wrapper(
26
20
  "anthropic.resources.messages",
27
21
  "Messages.create",
@@ -34,6 +28,18 @@ class AnthropicIntrumentor:
34
28
  chat_wrapper(instrumentor),
35
29
  )
36
30
 
31
+ wrap_function_wrapper(
32
+ "anthropic.resources.messages",
33
+ "AsyncMessages.create",
34
+ achat_wrapper(instrumentor),
35
+ )
36
+
37
+ wrap_function_wrapper(
38
+ "anthropic.resources.messages",
39
+ "AsyncMessages.stream",
40
+ achat_wrapper(instrumentor),
41
+ )
42
+
37
43
  except Exception as e:
38
44
  logging.debug(f"Error instrumenting anthropic: {e}")
39
45
  return
@@ -44,14 +50,35 @@ def chat_wrapper(
44
50
  instrumentor: PayiInstrumentor,
45
51
  wrapped: Any,
46
52
  instance: Any,
47
- args: Any,
48
- kwargs: Any,
53
+ *args: Any,
54
+ **kwargs: Any,
49
55
  ) -> Any:
50
56
  return instrumentor.chat_wrapper(
51
57
  "system.anthropic",
52
58
  process_chunk,
53
59
  process_request,
54
60
  process_synchronous_response,
61
+ IsStreaming.kwargs,
62
+ wrapped,
63
+ instance,
64
+ args,
65
+ kwargs,
66
+ )
67
+
68
+ @PayiInstrumentor.payi_awrapper
69
+ async def achat_wrapper(
70
+ instrumentor: PayiInstrumentor,
71
+ wrapped: Any,
72
+ instance: Any,
73
+ *args: Any,
74
+ **kwargs: Any,
75
+ ) -> Any:
76
+ return await instrumentor.achat_wrapper(
77
+ "system.anthropic",
78
+ process_chunk,
79
+ process_request,
80
+ process_synchronous_response,
81
+ IsStreaming.kwargs,
55
82
  wrapped,
56
83
  instance,
57
84
  args,
@@ -81,10 +108,10 @@ def process_chunk(chunk: Any, ingest: IngestUnitsParams) -> None:
81
108
  ingest["units"]["text"]["output"] = usage.output_tokens
82
109
 
83
110
 
84
- def process_synchronous_response(response: Any, ingest: IngestUnitsParams, log_prompt_and_response: bool) -> None:
111
+ def process_synchronous_response(response: Any, ingest: IngestUnitsParams, log_prompt_and_response: bool, *args: Any, **kwargs: 'dict[str, Any]') -> Any: # noqa: ARG001
85
112
  usage = response.usage
86
113
  input = usage.input_tokens
87
- ouptut = usage.output_tokens
114
+ output = usage.output_tokens
88
115
  units: dict[str, Units] = ingest["units"]
89
116
 
90
117
  if hasattr(usage, "cache_creation_input_tokens") and usage.cache_creation_input_tokens > 0:
@@ -97,11 +124,13 @@ def process_synchronous_response(response: Any, ingest: IngestUnitsParams, log_p
97
124
 
98
125
  input = PayiInstrumentor.update_for_vision(input, units)
99
126
 
100
- units["text"] = Units(input=input, output=ouptut)
127
+ units["text"] = Units(input=input, output=output)
101
128
 
102
129
  if log_prompt_and_response:
103
130
  ingest["provider_response_json"] = response.to_json()
104
131
 
132
+ return None
133
+
105
134
  def has_image_and_get_texts(encoding: tiktoken.Encoding, content: Union[str, 'list[Any]']) -> 'tuple[bool, int]':
106
135
  if isinstance(content, str):
107
136
  return False, 0
@@ -113,7 +142,7 @@ def has_image_and_get_texts(encoding: tiktoken.Encoding, content: Union[str, 'li
113
142
  token_count = sum(len(encoding.encode(item.get("text", ""))) for item in content if item.get("type") == "text")
114
143
  return has_image, token_count
115
144
 
116
- def process_request(ingest: IngestUnitsParams, kwargs: Any) -> None:
145
+ def process_request(ingest: IngestUnitsParams, *args: Any, **kwargs: Any) -> None: # noqa: ARG001
117
146
  messages = kwargs.get("messages")
118
147
  if not messages or len(messages) == 0:
119
148
  return
@@ -0,0 +1,282 @@
1
+ import json
2
+ import logging
3
+ from typing import Any
4
+ from functools import wraps
5
+
6
+ from wrapt import ObjectProxy, wrap_function_wrapper # type: ignore
7
+
8
+ from payi.types.ingest_units_params import Units, IngestUnitsParams
9
+ from payi.types.pay_i_common_models_api_router_header_info_param import PayICommonModelsAPIRouterHeaderInfoParam
10
+
11
+ from .instrument import IsStreaming, PayiInstrumentor
12
+
13
+
14
+ class BedrockInstrumentor:
15
+ @staticmethod
16
+ def instrument(instrumentor: PayiInstrumentor) -> None:
17
+ try:
18
+ import boto3 # type: ignore # noqa: F401 I001
19
+
20
+ wrap_function_wrapper(
21
+ "botocore.client",
22
+ "ClientCreator.create_client",
23
+ create_client_wrapper(instrumentor),
24
+ )
25
+
26
+ wrap_function_wrapper(
27
+ "botocore.session",
28
+ "Session.create_client",
29
+ create_client_wrapper(instrumentor),
30
+ )
31
+
32
+ except Exception as e:
33
+ logging.debug(f"Error instrumenting bedrock: {e}")
34
+ return
35
+
36
+ @PayiInstrumentor.payi_wrapper
37
+ def create_client_wrapper(instrumentor: PayiInstrumentor, wrapped: Any, instance: Any, *args: Any, **kwargs: Any) -> Any: # noqa: ARG001
38
+ if kwargs.get("service_name") != "bedrock-runtime":
39
+ return wrapped(*args, **kwargs)
40
+
41
+ try:
42
+ client: Any = wrapped(*args, **kwargs)
43
+ client.invoke_model = wrap_invoke(instrumentor, client.invoke_model)
44
+ client.invoke_model_with_response_stream = wrap_invoke_stream(instrumentor, client.invoke_model_with_response_stream)
45
+ client.converse = wrap_converse(instrumentor, client.converse)
46
+ client.converse_stream = wrap_converse_stream(instrumentor, client.converse_stream)
47
+
48
+ return client
49
+ except Exception as e:
50
+ logging.debug(f"Error instrumenting bedrock client: {e}")
51
+
52
+ return wrapped(*args, **kwargs)
53
+
54
+ class InvokeResponseWrapper(ObjectProxy): # type: ignore
55
+ def __init__(
56
+ self,
57
+ response: Any,
58
+ instrumentor: PayiInstrumentor,
59
+ ingest: IngestUnitsParams,
60
+ log_prompt_and_response: bool
61
+ ) -> None:
62
+
63
+ super().__init__(response) # type: ignore
64
+ self._response = response
65
+ self._instrumentor = instrumentor
66
+ self._ingest = ingest
67
+ self._log_prompt_and_response = log_prompt_and_response
68
+
69
+ def read(self, amt: Any =None): # type: ignore
70
+ # data is array of bytes
71
+ data: Any = self.__wrapped__.read(amt) # type: ignore
72
+ response = json.loads(data)
73
+
74
+ resource = self._ingest["resource"]
75
+ if not resource:
76
+ return
77
+
78
+ input: int = 0
79
+ output: int = 0
80
+ units: dict[str, Units] = self._ingest["units"]
81
+
82
+ if resource.startswith("meta.llama3"):
83
+ input = response['prompt_token_count']
84
+ output = response['generation_token_count']
85
+ elif resource.startswith("anthropic."):
86
+ usage = response['usage']
87
+ input = usage['input_tokens']
88
+ output = usage['output_tokens']
89
+ units["text"] = Units(input=input, output=output)
90
+
91
+ if self._log_prompt_and_response:
92
+ self._ingest["provider_response_json"] = data.decode('utf-8')
93
+
94
+ self._instrumentor._ingest_units(self._ingest)
95
+
96
+ return data
97
+
98
+ def wrap_invoke(instrumentor: PayiInstrumentor, wrapped: Any) -> Any:
99
+ @wraps(wrapped)
100
+ def invoke_wrapper(*args: Any, **kwargs: 'dict[str, Any]') -> Any:
101
+ modelId:str = kwargs.get("modelId", "") # type: ignore
102
+
103
+ if modelId.startswith("meta.llama3") or modelId.startswith("anthropic."):
104
+ return instrumentor.chat_wrapper(
105
+ "system.aws.bedrock",
106
+ None,
107
+ process_invoke_request,
108
+ process_synchronous_invoke_response,
109
+ IsStreaming.false,
110
+ wrapped,
111
+ None,
112
+ args,
113
+ kwargs,
114
+ )
115
+ return wrapped(*args, **kwargs)
116
+
117
+ return invoke_wrapper
118
+
119
+ def wrap_invoke_stream(instrumentor: PayiInstrumentor, wrapped: Any) -> Any:
120
+ @wraps(wrapped)
121
+ def invoke_wrapper(*args: Any, **kwargs: Any) -> Any:
122
+ modelId:str = kwargs.get("modelId", "") # type: ignore
123
+
124
+ if modelId.startswith("meta.llama3") or modelId.startswith("anthropic."):
125
+ return instrumentor.chat_wrapper(
126
+ "system.aws.bedrock",
127
+ process_invoke_streaming_anthropic_chunk if modelId.startswith("anthropic.") else process_invoke_streaming_llama_chunk,
128
+ process_invoke_request,
129
+ None,
130
+ IsStreaming.true,
131
+ wrapped,
132
+ None,
133
+ args,
134
+ kwargs,
135
+ )
136
+ return wrapped(*args, **kwargs)
137
+
138
+ return invoke_wrapper
139
+
140
+ def wrap_converse(instrumentor: PayiInstrumentor, wrapped: Any) -> Any:
141
+ @wraps(wrapped)
142
+ def invoke_wrapper(*args: Any, **kwargs: 'dict[str, Any]') -> Any:
143
+ modelId:str = kwargs.get("modelId", "") # type: ignore
144
+
145
+ if modelId.startswith("meta.llama3") or modelId.startswith("anthropic."):
146
+ return instrumentor.chat_wrapper(
147
+ "system.aws.bedrock",
148
+ None,
149
+ process_converse_request,
150
+ process_synchronous_converse_response,
151
+ IsStreaming.false,
152
+ wrapped,
153
+ None,
154
+ args,
155
+ kwargs,
156
+ )
157
+ return wrapped(*args, **kwargs)
158
+
159
+ return invoke_wrapper
160
+
161
+ def wrap_converse_stream(instrumentor: PayiInstrumentor, wrapped: Any) -> Any:
162
+ @wraps(wrapped)
163
+ def invoke_wrapper(*args: Any, **kwargs: Any) -> Any:
164
+ modelId:str = kwargs.get("modelId", "") # type: ignore
165
+
166
+ if modelId.startswith("meta.llama3") or modelId.startswith("anthropic."):
167
+ return instrumentor.chat_wrapper(
168
+ "system.aws.bedrock",
169
+ process_converse_streaming_chunk,
170
+ process_converse_request,
171
+ None,
172
+ IsStreaming.true,
173
+ wrapped,
174
+ None,
175
+ args,
176
+ kwargs,
177
+ )
178
+ return wrapped(*args, **kwargs)
179
+
180
+ return invoke_wrapper
181
+
182
+ def process_invoke_streaming_anthropic_chunk(chunk: str, ingest: IngestUnitsParams) -> None:
183
+ chunk_dict = json.loads(chunk)
184
+ type = chunk_dict.get("type", "")
185
+
186
+ if type == "message_start":
187
+ usage = chunk_dict['message']['usage']
188
+ units = ingest["units"]
189
+
190
+ input = PayiInstrumentor.update_for_vision(usage['input_tokens'], units)
191
+
192
+ units["text"] = Units(input=input, output=0)
193
+
194
+ text_cache_write: int = usage.get("cache_creation_input_tokens", 0)
195
+ if text_cache_write > 0:
196
+ units["text_cache_write"] = Units(input=text_cache_write, output=0)
197
+
198
+ text_cache_read: int = usage.get("cache_read_input_tokens", 0)
199
+ if text_cache_read > 0:
200
+ units["text_cache_read"] = Units(input=text_cache_read, output=0)
201
+
202
+ elif type == "message_delta":
203
+ usage = chunk_dict['usage']
204
+ ingest["units"]["text"]["output"] = usage['output_tokens']
205
+
206
+ def process_invoke_streaming_llama_chunk(chunk: str, ingest: IngestUnitsParams) -> None:
207
+ chunk_dict = json.loads(chunk)
208
+ metrics = chunk_dict.get("amazon-bedrock-invocationMetrics", {})
209
+ if metrics:
210
+ input = metrics.get("inputTokenCount", 0)
211
+ output = metrics.get("outputTokenCount", 0)
212
+ ingest["units"]["text"] = Units(input=input, output=output)
213
+
214
+ def process_synchronous_invoke_response(
215
+ response: Any,
216
+ ingest: IngestUnitsParams,
217
+ log_prompt_and_response: bool,
218
+ instrumentor: PayiInstrumentor,
219
+ **kargs: Any) -> Any: # noqa: ARG001
220
+
221
+ metadata = response.get("ResponseMetadata", {})
222
+
223
+ # request_id = metadata.get("RequestId", "")
224
+ # if request_id:
225
+ # ingest["provider_request_id"] = request_id
226
+
227
+ response_headers = metadata.get("HTTPHeaders", {}).copy()
228
+ if response_headers:
229
+ ingest["provider_response_headers"] = [PayICommonModelsAPIRouterHeaderInfoParam(name=k, value=v) for k, v in response_headers.items()]
230
+
231
+ response["body"] = InvokeResponseWrapper(
232
+ response=response["body"],
233
+ instrumentor=instrumentor,
234
+ ingest=ingest,
235
+ log_prompt_and_response=log_prompt_and_response)
236
+
237
+ return response
238
+
239
+ def process_invoke_request(ingest: IngestUnitsParams, *args: Any, **kwargs: Any) -> None: # noqa: ARG001
240
+ return
241
+
242
+ def process_converse_streaming_chunk(chunk: 'dict[str, Any]', ingest: IngestUnitsParams) -> None:
243
+ metadata = chunk.get("metadata", {})
244
+
245
+ if metadata:
246
+ usage = metadata['usage']
247
+ input = usage["inputTokens"]
248
+ output = usage["outputTokens"]
249
+ ingest["units"]["text"] = Units(input=input, output=output)
250
+
251
+ def process_synchronous_converse_response(
252
+ response: 'dict[str, Any]',
253
+ ingest: IngestUnitsParams,
254
+ log_prompt_and_response: bool,
255
+ **kargs: Any) -> Any: # noqa: ARG001
256
+
257
+ usage = response["usage"]
258
+ input = usage["inputTokens"]
259
+ output = usage["outputTokens"]
260
+
261
+ units: dict[str, Units] = ingest["units"]
262
+ units["text"] = Units(input=input, output=output)
263
+
264
+ metadata = response.get("ResponseMetadata", {})
265
+
266
+ # request_id = metadata.get("RequestId", "")
267
+ # if request_id:
268
+ # ingest["provider_request_id"] = request_id
269
+
270
+ response_headers = metadata.get("HTTPHeaders", {})
271
+ if response_headers:
272
+ ingest["provider_response_headers"] = [PayICommonModelsAPIRouterHeaderInfoParam(name=k, value=v) for k, v in response_headers.items()]
273
+
274
+ if log_prompt_and_response:
275
+ response_without_metadata = response.copy()
276
+ response_without_metadata.pop("ResponseMetadata", None)
277
+ ingest["provider_response_json"] = json.dumps(response_without_metadata)
278
+
279
+ return None
280
+
281
+ def process_converse_request(ingest: IngestUnitsParams, *args: Any, **kwargs: Any) -> None: # noqa: ARG001
282
+ return
payi/lib/Instruments.py CHANGED
@@ -5,3 +5,4 @@ class Instruments(Enum):
5
5
  ALL = "all"
6
6
  OPENAI = "openai"
7
7
  ANTHROPIC = "anthropic"
8
+ AWS_BEDROCK = "aws.bedrock"
@@ -3,13 +3,13 @@ import logging
3
3
  from typing import Any, Union
4
4
  from importlib.metadata import version
5
5
 
6
- import tiktoken
6
+ import tiktoken # type: ignore
7
7
  from wrapt import wrap_function_wrapper # type: ignore
8
8
 
9
9
  from payi.types import IngestUnitsParams
10
10
  from payi.types.ingest_units_params import Units
11
11
 
12
- from .instrument import PayiInstrumentor
12
+ from .instrument import IsStreaming, PayiInstrumentor
13
13
 
14
14
 
15
15
  class OpenAiInstrumentor:
@@ -23,6 +23,13 @@ class OpenAiInstrumentor:
23
23
  "Completions.create",
24
24
  chat_wrapper(instrumentor),
25
25
  )
26
+
27
+ wrap_function_wrapper(
28
+ "openai.resources.chat.completions",
29
+ "AsyncCompletions.create",
30
+ achat_wrapper(instrumentor),
31
+ )
32
+
26
33
  except Exception as e:
27
34
  logging.debug(f"Error instrumenting openai: {e}")
28
35
  return
@@ -33,14 +40,35 @@ def chat_wrapper(
33
40
  instrumentor: PayiInstrumentor,
34
41
  wrapped: Any,
35
42
  instance: Any,
36
- args: Any,
37
- kwargs: Any,
43
+ *args: Any,
44
+ **kwargs: Any,
38
45
  ) -> Any:
39
46
  return instrumentor.chat_wrapper(
40
47
  "system.openai",
41
48
  process_chat_chunk,
42
49
  process_request,
43
50
  process_chat_synchronous_response,
51
+ IsStreaming.kwargs,
52
+ wrapped,
53
+ instance,
54
+ args,
55
+ kwargs,
56
+ )
57
+
58
+ @PayiInstrumentor.payi_awrapper
59
+ async def achat_wrapper(
60
+ instrumentor: PayiInstrumentor,
61
+ wrapped: Any,
62
+ instance: Any,
63
+ *args: Any,
64
+ **kwargs: Any,
65
+ ) -> Any:
66
+ return await instrumentor.achat_wrapper(
67
+ "system.openai",
68
+ process_chat_chunk,
69
+ process_request,
70
+ process_chat_synchronous_response,
71
+ IsStreaming.kwargs,
44
72
  wrapped,
45
73
  instance,
46
74
  args,
@@ -48,7 +76,7 @@ def chat_wrapper(
48
76
  )
49
77
 
50
78
 
51
- def process_chat_synchronous_response(response: str, ingest: IngestUnitsParams, log_prompt_and_response: bool) -> None:
79
+ def process_chat_synchronous_response(response: str, ingest: IngestUnitsParams, log_prompt_and_response: bool, **kwargs: Any) -> Any: # noqa: ARG001
52
80
  response_dict = model_to_dict(response)
53
81
 
54
82
  add_usage_units(response_dict["usage"], ingest["units"])
@@ -56,6 +84,7 @@ def process_chat_synchronous_response(response: str, ingest: IngestUnitsParams,
56
84
  if log_prompt_and_response:
57
85
  ingest["provider_response_json"] = [json.dumps(response_dict)]
58
86
 
87
+ return None
59
88
 
60
89
  def process_chat_chunk(chunk: Any, ingest: IngestUnitsParams) -> None:
61
90
  model = model_to_dict(chunk)
@@ -101,7 +130,7 @@ def has_image_and_get_texts(encoding: tiktoken.Encoding, content: Union[str, 'li
101
130
  token_count = sum(len(encoding.encode(item.get("text", ""))) for item in content if item.get("type") == "text")
102
131
  return has_image, token_count
103
132
 
104
- def process_request(ingest: IngestUnitsParams, kwargs: Any) -> None:
133
+ def process_request(ingest: IngestUnitsParams, *args: Any, **kwargs: Any) -> None: # noqa: ARG001
105
134
  messages = kwargs.get("messages")
106
135
  if not messages or len(messages) == 0:
107
136
  return
@@ -110,9 +139,9 @@ def process_request(ingest: IngestUnitsParams, kwargs: Any) -> None:
110
139
  has_image = False
111
140
 
112
141
  try:
113
- enc = tiktoken.encoding_for_model(kwargs.get("model"))
142
+ enc = tiktoken.encoding_for_model(kwargs.get("model")) # type: ignore
114
143
  except KeyError:
115
- enc = tiktoken.get_encoding("o200k_base")
144
+ enc = tiktoken.get_encoding("o200k_base") # type: ignore
116
145
 
117
146
  for message in messages:
118
147
  msg_has_image, msg_prompt_tokens = has_image_and_get_texts(enc, message.get('content', ''))
payi/lib/Stopwatch.py CHANGED
@@ -15,7 +15,7 @@ class Stopwatch:
15
15
 
16
16
  def elapsed_s(self) -> float:
17
17
  if self.start_time is None:
18
- raise ValueError("Stopwatch has not been started")
18
+ return 0.0 # ValueError("Stopwatch has not been started")
19
19
  if self.end_time is None:
20
20
  return time.perf_counter() - self.start_time
21
21
  return self.end_time - self.start_time