payi 0.1.0a63__py3-none-any.whl → 0.1.0a65__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/_utils/_transform.py CHANGED
@@ -142,6 +142,10 @@ def _maybe_transform_key(key: str, type_: type) -> str:
142
142
  return key
143
143
 
144
144
 
145
+ def _no_transform_needed(annotation: type) -> bool:
146
+ return annotation == float or annotation == int
147
+
148
+
145
149
  def _transform_recursive(
146
150
  data: object,
147
151
  *,
@@ -184,6 +188,15 @@ def _transform_recursive(
184
188
  return cast(object, data)
185
189
 
186
190
  inner_type = extract_type_arg(stripped_type, 0)
191
+ if _no_transform_needed(inner_type):
192
+ # for some types there is no need to transform anything, so we can get a small
193
+ # perf boost from skipping that work.
194
+ #
195
+ # but we still need to convert to a list to ensure the data is json-serializable
196
+ if is_list(data):
197
+ return data
198
+ return list(data)
199
+
187
200
  return [_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
188
201
 
189
202
  if is_union_type(stripped_type):
@@ -332,6 +345,15 @@ async def _async_transform_recursive(
332
345
  return cast(object, data)
333
346
 
334
347
  inner_type = extract_type_arg(stripped_type, 0)
348
+ if _no_transform_needed(inner_type):
349
+ # for some types there is no need to transform anything, so we can get a small
350
+ # perf boost from skipping that work.
351
+ #
352
+ # but we still need to convert to a list to ensure the data is json-serializable
353
+ if is_list(data):
354
+ return data
355
+ return list(data)
356
+
335
357
  return [await _async_transform_recursive(d, annotation=annotation, inner_type=inner_type) for d in data]
336
358
 
337
359
  if is_union_type(stripped_type):
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.63" # x-release-please-version
4
+ __version__ = "0.1.0-alpha.65" # x-release-please-version
@@ -1,13 +1,13 @@
1
1
  import logging
2
2
  from typing import Any, Union
3
+ from typing_extensions import override
3
4
 
4
5
  import tiktoken
5
6
  from wrapt import wrap_function_wrapper # type: ignore
6
7
 
7
- from payi.types import IngestUnitsParams
8
8
  from payi.types.ingest_units_params import Units
9
9
 
10
- from .instrument import _IsStreaming, _PayiInstrumentor
10
+ from .instrument import _IsStreaming, _ProviderRequest, _PayiInstrumentor
11
11
 
12
12
 
13
13
  class AnthropicIntrumentor:
@@ -55,9 +55,7 @@ def chat_wrapper(
55
55
  ) -> Any:
56
56
  return instrumentor.chat_wrapper(
57
57
  "system.anthropic",
58
- process_chunk,
59
- process_request,
60
- process_synchronous_response,
58
+ _AnthropicProviderRequest(instrumentor),
61
59
  _IsStreaming.kwargs,
62
60
  wrapped,
63
61
  instance,
@@ -75,9 +73,7 @@ async def achat_wrapper(
75
73
  ) -> Any:
76
74
  return await instrumentor.achat_wrapper(
77
75
  "system.anthropic",
78
- process_chunk,
79
- process_request,
80
- process_synchronous_response,
76
+ _AnthropicProviderRequest(instrumentor),
81
77
  _IsStreaming.kwargs,
82
78
  wrapped,
83
79
  instance,
@@ -85,17 +81,39 @@ async def achat_wrapper(
85
81
  kwargs,
86
82
  )
87
83
 
84
+ class _AnthropicProviderRequest(_ProviderRequest):
85
+ @override
86
+ def process_chunk(self, chunk: Any) -> bool:
87
+ if chunk.type == "message_start":
88
+ self._ingest["provider_response_id"] = chunk.message.id
88
89
 
89
- def process_chunk(chunk: Any, ingest: IngestUnitsParams) -> None:
90
- if chunk.type == "message_start":
91
- ingest["provider_response_id"] = chunk.message.id
90
+ usage = chunk.message.usage
91
+ units = self._ingest["units"]
92
92
 
93
- usage = chunk.message.usage
94
- units = ingest["units"]
93
+ input = _PayiInstrumentor.update_for_vision(usage.input_tokens, units, self._estimated_prompt_tokens)
95
94
 
96
- input = _PayiInstrumentor.update_for_vision(usage.input_tokens, units)
95
+ units["text"] = Units(input=input, output=0)
97
96
 
98
- units["text"] = Units(input=input, output=0)
97
+ if hasattr(usage, "cache_creation_input_tokens") and usage.cache_creation_input_tokens > 0:
98
+ text_cache_write = usage.cache_creation_input_tokens
99
+ units["text_cache_write"] = Units(input=text_cache_write, output=0)
100
+
101
+ if hasattr(usage, "cache_read_input_tokens") and usage.cache_read_input_tokens > 0:
102
+ text_cache_read = usage.cache_read_input_tokens
103
+ units["text_cache_read"] = Units(input=text_cache_read, output=0)
104
+
105
+ elif chunk.type == "message_delta":
106
+ usage = chunk.usage
107
+ self._ingest["units"]["text"]["output"] = usage.output_tokens
108
+
109
+ return True
110
+
111
+ @override
112
+ def process_synchronous_response(self, response: Any, log_prompt_and_response: bool, kwargs: Any) -> Any:
113
+ usage = response.usage
114
+ input = usage.input_tokens
115
+ output = usage.output_tokens
116
+ units: dict[str, Units] = self._ingest["units"]
99
117
 
100
118
  if hasattr(usage, "cache_creation_input_tokens") and usage.cache_creation_input_tokens > 0:
101
119
  text_cache_write = usage.cache_creation_input_tokens
@@ -105,35 +123,37 @@ def process_chunk(chunk: Any, ingest: IngestUnitsParams) -> None:
105
123
  text_cache_read = usage.cache_read_input_tokens
106
124
  units["text_cache_read"] = Units(input=text_cache_read, output=0)
107
125
 
108
- elif chunk.type == "message_delta":
109
- usage = chunk.usage
110
- ingest["units"]["text"]["output"] = usage.output_tokens
111
-
112
-
113
- def process_synchronous_response(response: Any, ingest: IngestUnitsParams, log_prompt_and_response: bool, *args: Any, **kwargs: 'dict[str, Any]') -> Any: # noqa: ARG001
114
- usage = response.usage
115
- input = usage.input_tokens
116
- output = usage.output_tokens
117
- units: dict[str, Units] = ingest["units"]
118
-
119
- if hasattr(usage, "cache_creation_input_tokens") and usage.cache_creation_input_tokens > 0:
120
- text_cache_write = usage.cache_creation_input_tokens
121
- units["text_cache_write"] = Units(input=text_cache_write, output=0)
126
+ input = _PayiInstrumentor.update_for_vision(input, units, self._estimated_prompt_tokens)
122
127
 
123
- if hasattr(usage, "cache_read_input_tokens") and usage.cache_read_input_tokens > 0:
124
- text_cache_read = usage.cache_read_input_tokens
125
- units["text_cache_read"] = Units(input=text_cache_read, output=0)
128
+ units["text"] = Units(input=input, output=output)
126
129
 
127
- input = _PayiInstrumentor.update_for_vision(input, units)
130
+ if log_prompt_and_response:
131
+ self._ingest["provider_response_json"] = response.to_json()
132
+
133
+ self._ingest["provider_response_id"] = response.id
134
+
135
+ return None
128
136
 
129
- units["text"] = Units(input=input, output=output)
137
+ @override
138
+ def process_request(self, kwargs: Any) -> None:
139
+ messages = kwargs.get("messages")
140
+ if not messages or len(messages) == 0:
141
+ return
142
+
143
+ estimated_token_count = 0
144
+ has_image = False
130
145
 
131
- if log_prompt_and_response:
132
- ingest["provider_response_json"] = response.to_json()
133
-
134
- ingest["provider_response_id"] = response.id
135
-
136
- return None
146
+ enc = tiktoken.get_encoding("cl100k_base")
147
+
148
+ for message in messages:
149
+ msg_has_image, msg_prompt_tokens = has_image_and_get_texts(enc, message.get('content', ''))
150
+ if msg_has_image:
151
+ has_image = True
152
+ estimated_token_count += msg_prompt_tokens
153
+
154
+ if not has_image or estimated_token_count == 0:
155
+ return
156
+ self._estimated_prompt_tokens = estimated_token_count
137
157
 
138
158
  def has_image_and_get_texts(encoding: tiktoken.Encoding, content: Union[str, 'list[Any]']) -> 'tuple[bool, int]':
139
159
  if isinstance(content, str):
@@ -146,23 +166,3 @@ def has_image_and_get_texts(encoding: tiktoken.Encoding, content: Union[str, 'li
146
166
  token_count = sum(len(encoding.encode(item.get("text", ""))) for item in content if item.get("type") == "text")
147
167
  return has_image, token_count
148
168
 
149
- def process_request(ingest: IngestUnitsParams, *args: Any, **kwargs: Any) -> None: # noqa: ARG001
150
- messages = kwargs.get("messages")
151
- if not messages or len(messages) == 0:
152
- return
153
-
154
- estimated_token_count = 0
155
- has_image = False
156
-
157
- enc = tiktoken.get_encoding("cl100k_base")
158
-
159
- for message in messages:
160
- msg_has_image, msg_prompt_tokens = has_image_and_get_texts(enc, message.get('content', ''))
161
- if msg_has_image:
162
- has_image = True
163
- estimated_token_count += msg_prompt_tokens
164
-
165
- if not has_image or estimated_token_count == 0:
166
- return
167
-
168
- ingest["units"][_PayiInstrumentor.estimated_prompt_tokens] = Units(input=estimated_token_count, output=0)
@@ -2,13 +2,14 @@ import json
2
2
  import logging
3
3
  from typing import Any
4
4
  from functools import wraps
5
+ from typing_extensions import override
5
6
 
6
7
  from wrapt import ObjectProxy, wrap_function_wrapper # type: ignore
7
8
 
8
9
  from payi.types.ingest_units_params import Units, IngestUnitsParams
9
10
  from payi.types.pay_i_common_models_api_router_header_info_param import PayICommonModelsAPIRouterHeaderInfoParam
10
11
 
11
- from .instrument import _IsStreaming, _PayiInstrumentor
12
+ from .instrument import _IsStreaming, _ProviderRequest, _PayiInstrumentor
12
13
 
13
14
 
14
15
  class BedrockInstrumentor:
@@ -103,9 +104,7 @@ def wrap_invoke(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
103
104
  if modelId.startswith("meta.llama3") or modelId.startswith("anthropic."):
104
105
  return instrumentor.chat_wrapper(
105
106
  "system.aws.bedrock",
106
- None,
107
- process_invoke_request,
108
- process_synchronous_invoke_response,
107
+ _BedrockInvokeSynchronousProviderRequest(instrumentor),
109
108
  _IsStreaming.false,
110
109
  wrapped,
111
110
  None,
@@ -119,14 +118,12 @@ def wrap_invoke(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
119
118
  def wrap_invoke_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
120
119
  @wraps(wrapped)
121
120
  def invoke_wrapper(*args: Any, **kwargs: Any) -> Any:
122
- modelId:str = kwargs.get("modelId", "") # type: ignore
121
+ model_id: str = kwargs.get("modelId", "") # type: ignore
123
122
 
124
- if modelId.startswith("meta.llama3") or modelId.startswith("anthropic."):
123
+ if model_id.startswith("meta.llama3") or model_id.startswith("anthropic."):
125
124
  return instrumentor.chat_wrapper(
126
125
  "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,
126
+ _BedrockInvokeStreamingProviderRequest(instrumentor, model_id),
130
127
  _IsStreaming.true,
131
128
  wrapped,
132
129
  None,
@@ -145,9 +142,7 @@ def wrap_converse(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
145
142
  if modelId.startswith("meta.llama3") or modelId.startswith("anthropic."):
146
143
  return instrumentor.chat_wrapper(
147
144
  "system.aws.bedrock",
148
- None,
149
- process_converse_request,
150
- process_synchronous_converse_response,
145
+ _BedrockConverseSynchronousProviderRequest(instrumentor),
151
146
  _IsStreaming.false,
152
147
  wrapped,
153
148
  None,
@@ -161,14 +156,12 @@ def wrap_converse(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
161
156
  def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
162
157
  @wraps(wrapped)
163
158
  def invoke_wrapper(*args: Any, **kwargs: Any) -> Any:
164
- modelId:str = kwargs.get("modelId", "") # type: ignore
159
+ model_id: str = kwargs.get("modelId", "") # type: ignore
165
160
 
166
- if modelId.startswith("meta.llama3") or modelId.startswith("anthropic."):
161
+ if model_id.startswith("meta.llama3") or model_id.startswith("anthropic."):
167
162
  return instrumentor.chat_wrapper(
168
163
  "system.aws.bedrock",
169
- process_converse_streaming_chunk,
170
- process_converse_request,
171
- None,
164
+ _BedrockConverseStreamingProviderRequest(instrumentor),
172
165
  _IsStreaming.true,
173
166
  wrapped,
174
167
  None,
@@ -179,104 +172,121 @@ def wrap_converse_stream(instrumentor: _PayiInstrumentor, wrapped: Any) -> Any:
179
172
 
180
173
  return invoke_wrapper
181
174
 
182
- def process_invoke_streaming_anthropic_chunk(chunk: str, ingest: IngestUnitsParams) -> None:
183
- chunk_dict = json.loads(chunk)
184
- type = chunk_dict.get("type", "")
175
+ class _BedrockInvokeStreamingProviderRequest(_ProviderRequest):
176
+ def __init__(self, instrumentor: _PayiInstrumentor, model_id: str):
177
+ super().__init__(instrumentor)
178
+ self._is_anthropic: bool = model_id.startswith("anthropic.")
185
179
 
186
- if type == "message_start":
187
- usage = chunk_dict['message']['usage']
188
- units = ingest["units"]
180
+ @override
181
+ def process_chunk(self, chunk: Any) -> bool:
182
+ if self._is_anthropic:
183
+ return self.process_invoke_streaming_anthropic_chunk(chunk)
184
+ else:
185
+ return self.process_invoke_streaming_llama_chunk(chunk)
189
186
 
190
- input = _PayiInstrumentor.update_for_vision(usage['input_tokens'], units)
187
+ def process_invoke_streaming_anthropic_chunk(self, chunk: str) -> bool:
188
+ chunk_dict = json.loads(chunk)
189
+ type = chunk_dict.get("type", "")
191
190
 
192
- units["text"] = Units(input=input, output=0)
191
+ if type == "message_start":
192
+ usage = chunk_dict['message']['usage']
193
+ units = self._ingest["units"]
193
194
 
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)
195
+ input = _PayiInstrumentor.update_for_vision(usage['input_tokens'], units, self._estimated_prompt_tokens)
197
196
 
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)
197
+ units["text"] = Units(input=input, output=0)
201
198
 
202
- elif type == "message_delta":
203
- usage = chunk_dict['usage']
204
- ingest["units"]["text"]["output"] = usage['output_tokens']
199
+ text_cache_write: int = usage.get("cache_creation_input_tokens", 0)
200
+ if text_cache_write > 0:
201
+ units["text_cache_write"] = Units(input=text_cache_write, output=0)
205
202
 
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(
203
+ text_cache_read: int = usage.get("cache_read_input_tokens", 0)
204
+ if text_cache_read > 0:
205
+ units["text_cache_read"] = Units(input=text_cache_read, output=0)
206
+
207
+ elif type == "message_delta":
208
+ usage = chunk_dict['usage']
209
+ self._ingest["units"]["text"]["output"] = usage['output_tokens']
210
+
211
+ return True
212
+
213
+ def process_invoke_streaming_llama_chunk(self, chunk: str) -> bool:
214
+ chunk_dict = json.loads(chunk)
215
+ metrics = chunk_dict.get("amazon-bedrock-invocationMetrics", {})
216
+ if metrics:
217
+ input = metrics.get("inputTokenCount", 0)
218
+ output = metrics.get("outputTokenCount", 0)
219
+ self._ingest["units"]["text"] = Units(input=input, output=output)
220
+
221
+ return True
222
+
223
+ class _BedrockInvokeSynchronousProviderRequest(_ProviderRequest):
224
+ @override
225
+ def process_synchronous_response(
226
+ self,
215
227
  response: Any,
216
- ingest: IngestUnitsParams,
217
228
  log_prompt_and_response: bool,
218
- instrumentor: _PayiInstrumentor,
219
- **kargs: Any) -> Any: # noqa: ARG001
229
+ kwargs: Any) -> Any:
220
230
 
221
- metadata = response.get("ResponseMetadata", {})
231
+ metadata = response.get("ResponseMetadata", {})
222
232
 
223
- request_id = metadata.get("RequestId", "")
224
- if request_id:
225
- ingest["provider_response_id"] = request_id
233
+ request_id = metadata.get("RequestId", "")
234
+ if request_id:
235
+ self._ingest["provider_response_id"] = request_id
226
236
 
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()]
237
+ response_headers = metadata.get("HTTPHeaders", {}).copy()
238
+ if response_headers:
239
+ self._ingest["provider_response_headers"] = [PayICommonModelsAPIRouterHeaderInfoParam(name=k, value=v) for k, v in response_headers.items()]
230
240
 
231
- response["body"] = InvokeResponseWrapper(
232
- response=response["body"],
233
- instrumentor=instrumentor,
234
- ingest=ingest,
235
- log_prompt_and_response=log_prompt_and_response)
241
+ response["body"] = InvokeResponseWrapper(
242
+ response=response["body"],
243
+ instrumentor=self._instrumentor,
244
+ ingest=self._ingest,
245
+ log_prompt_and_response=log_prompt_and_response)
236
246
 
237
- return response
247
+ return response
238
248
 
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", {})
249
+ class _BedrockConverseSynchronousProviderRequest(_ProviderRequest):
250
+ @override
251
+ def process_synchronous_response(
252
+ self,
253
+ response: 'dict[str, Any]',
254
+ log_prompt_and_response: bool,
255
+ kwargs: Any) -> Any:
244
256
 
245
- if metadata:
246
- usage = metadata['usage']
257
+ usage = response["usage"]
247
258
  input = usage["inputTokens"]
248
259
  output = usage["outputTokens"]
249
- ingest["units"]["text"] = Units(input=input, output=output)
260
+
261
+ units: dict[str, Units] = self._ingest["units"]
262
+ units["text"] = Units(input=input, output=output)
250
263
 
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
264
+ metadata = response.get("ResponseMetadata", {})
256
265
 
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)
266
+ request_id = metadata.get("RequestId", "")
267
+ if request_id:
268
+ self._ingest["provider_response_id"] = request_id
263
269
 
264
- metadata = response.get("ResponseMetadata", {})
270
+ response_headers = metadata.get("HTTPHeaders", {})
271
+ if response_headers:
272
+ self._ingest["provider_response_headers"] = [PayICommonModelsAPIRouterHeaderInfoParam(name=k, value=v) for k, v in response_headers.items()]
265
273
 
266
- request_id = metadata.get("RequestId", "")
267
- if request_id:
268
- ingest["provider_response_id"] = request_id
274
+ if log_prompt_and_response:
275
+ response_without_metadata = response.copy()
276
+ response_without_metadata.pop("ResponseMetadata", None)
277
+ self._ingest["provider_response_json"] = json.dumps(response_without_metadata)
269
278
 
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()]
279
+ return None
273
280
 
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)
281
+ class _BedrockConverseStreamingProviderRequest(_ProviderRequest):
282
+ @override
283
+ def process_chunk(self, chunk: 'dict[str, Any]') -> bool:
284
+ metadata = chunk.get("metadata", {})
278
285
 
279
- return None
286
+ if metadata:
287
+ usage = metadata['usage']
288
+ input = usage["inputTokens"]
289
+ output = usage["outputTokens"]
290
+ self._ingest["units"]["text"] = Units(input=input, output=output)
280
291
 
281
- def process_converse_request(ingest: IngestUnitsParams, *args: Any, **kwargs: Any) -> None: # noqa: ARG001
282
- return
292
+ return True