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 +1 -1
- payi/_models.py +1 -1
- payi/_version.py +1 -1
- payi/lib/AnthropicInstrumentor.py +42 -13
- payi/lib/BedrockInstrumentor.py +282 -0
- payi/lib/Instruments.py +1 -0
- payi/lib/OpenAIInstrumentor.py +37 -8
- payi/lib/Stopwatch.py +1 -1
- payi/lib/instrument.py +505 -155
- payi/resources/ingest.py +78 -0
- payi/types/__init__.py +3 -0
- payi/types/bulk_ingest_response.py +51 -0
- payi/types/ingest_bulk_params.py +14 -0
- payi/types/ingest_event_param.py +60 -0
- {payi-0.1.0a40.dist-info → payi-0.1.0a42.dist-info}/METADATA +1 -1
- {payi-0.1.0a40.dist-info → payi-0.1.0a42.dist-info}/RECORD +18 -14
- {payi-0.1.0a40.dist-info → payi-0.1.0a42.dist-info}/WHEEL +0 -0
- {payi-0.1.0a40.dist-info → payi-0.1.0a42.dist-info}/licenses/LICENSE +0 -0
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
|
|
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
|
|
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
|
@@ -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) ->
|
|
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
|
-
|
|
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=
|
|
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
payi/lib/OpenAIInstrumentor.py
CHANGED
|
@@ -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) ->
|
|
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
|
-
|
|
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
|