tokenator 0.1.8__py3-none-any.whl → 0.1.10__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- tokenator/__init__.py +3 -3
- tokenator/anthropic/client_anthropic.py +155 -0
- tokenator/anthropic/stream_interceptors.py +146 -0
- tokenator/base_wrapper.py +26 -13
- tokenator/create_migrations.py +6 -5
- tokenator/migrations/env.py +5 -4
- tokenator/migrations/versions/f6f1f2437513_initial_migration.py +25 -23
- tokenator/migrations.py +9 -6
- tokenator/models.py +15 -4
- tokenator/openai/client_openai.py +163 -0
- tokenator/openai/stream_interceptors.py +146 -0
- tokenator/schemas.py +26 -27
- tokenator/usage.py +114 -47
- tokenator/utils.py +14 -9
- {tokenator-0.1.8.dist-info → tokenator-0.1.10.dist-info}/METADATA +40 -13
- tokenator-0.1.10.dist-info/RECORD +19 -0
- tokenator/client_anthropic.py +0 -148
- tokenator/client_openai.py +0 -151
- tokenator-0.1.8.dist-info/RECORD +0 -17
- {tokenator-0.1.8.dist-info → tokenator-0.1.10.dist-info}/LICENSE +0 -0
- {tokenator-0.1.8.dist-info → tokenator-0.1.10.dist-info}/WHEEL +0 -0
tokenator/client_openai.py
DELETED
@@ -1,151 +0,0 @@
|
|
1
|
-
"""OpenAI client wrapper with token usage tracking."""
|
2
|
-
|
3
|
-
from typing import Any, Dict, Optional, TypeVar, Union, overload, Iterator, AsyncIterator
|
4
|
-
import logging
|
5
|
-
|
6
|
-
from openai import AsyncOpenAI, AsyncStream, OpenAI, Stream
|
7
|
-
from openai.types.chat import ChatCompletion, ChatCompletionChunk
|
8
|
-
|
9
|
-
from .models import Usage, TokenUsageStats
|
10
|
-
from .base_wrapper import BaseWrapper, ResponseType
|
11
|
-
|
12
|
-
logger = logging.getLogger(__name__)
|
13
|
-
|
14
|
-
class BaseOpenAIWrapper(BaseWrapper):
|
15
|
-
provider = "openai"
|
16
|
-
|
17
|
-
def _process_response_usage(self, response: ResponseType) -> Optional[TokenUsageStats]:
|
18
|
-
"""Process and log usage statistics from a response."""
|
19
|
-
try:
|
20
|
-
if isinstance(response, ChatCompletion):
|
21
|
-
if response.usage is None:
|
22
|
-
return None
|
23
|
-
usage = Usage(
|
24
|
-
prompt_tokens=response.usage.prompt_tokens,
|
25
|
-
completion_tokens=response.usage.completion_tokens,
|
26
|
-
total_tokens=response.usage.total_tokens,
|
27
|
-
)
|
28
|
-
return TokenUsageStats(model=response.model, usage=usage)
|
29
|
-
|
30
|
-
elif isinstance(response, dict):
|
31
|
-
usage_dict = response.get('usage')
|
32
|
-
if not usage_dict:
|
33
|
-
return None
|
34
|
-
usage = Usage(
|
35
|
-
prompt_tokens=usage_dict.get('prompt_tokens', 0),
|
36
|
-
completion_tokens=usage_dict.get('completion_tokens', 0),
|
37
|
-
total_tokens=usage_dict.get('total_tokens', 0)
|
38
|
-
)
|
39
|
-
return TokenUsageStats(
|
40
|
-
model=response.get('model', 'unknown'),
|
41
|
-
usage=usage
|
42
|
-
)
|
43
|
-
except Exception as e:
|
44
|
-
logger.warning("Failed to process usage stats: %s", str(e))
|
45
|
-
return None
|
46
|
-
return None
|
47
|
-
|
48
|
-
@property
|
49
|
-
def chat(self):
|
50
|
-
return self
|
51
|
-
|
52
|
-
@property
|
53
|
-
def completions(self):
|
54
|
-
return self
|
55
|
-
|
56
|
-
class OpenAIWrapper(BaseOpenAIWrapper):
|
57
|
-
def create(self, *args: Any, execution_id: Optional[str] = None, **kwargs: Any) -> Union[ChatCompletion, Iterator[ChatCompletion]]:
|
58
|
-
"""Create a chat completion and log token usage."""
|
59
|
-
logger.debug("Creating chat completion with args: %s, kwargs: %s", args, kwargs)
|
60
|
-
|
61
|
-
response = self.client.chat.completions.create(*args, **kwargs)
|
62
|
-
|
63
|
-
if not kwargs.get('stream', False):
|
64
|
-
usage_data = self._process_response_usage(response)
|
65
|
-
if usage_data:
|
66
|
-
self._log_usage(usage_data, execution_id=execution_id)
|
67
|
-
return response
|
68
|
-
|
69
|
-
return self._wrap_streaming_response(response, execution_id)
|
70
|
-
|
71
|
-
def _wrap_streaming_response(self, response_iter: Stream[ChatCompletionChunk], execution_id: Optional[str]) -> Iterator[ChatCompletionChunk]:
|
72
|
-
"""Wrap streaming response to capture final usage stats"""
|
73
|
-
chunks_with_usage = []
|
74
|
-
for chunk in response_iter:
|
75
|
-
if isinstance(chunk, ChatCompletionChunk) and chunk.usage is not None:
|
76
|
-
chunks_with_usage.append(chunk)
|
77
|
-
yield chunk
|
78
|
-
|
79
|
-
if len(chunks_with_usage) > 0:
|
80
|
-
usage_data: TokenUsageStats = TokenUsageStats(model=chunks_with_usage[0].model, usage=Usage())
|
81
|
-
for chunk in chunks_with_usage:
|
82
|
-
usage_data.usage.prompt_tokens += chunk.usage.prompt_tokens
|
83
|
-
usage_data.usage.completion_tokens += chunk.usage.completion_tokens
|
84
|
-
usage_data.usage.total_tokens += chunk.usage.total_tokens
|
85
|
-
|
86
|
-
self._log_usage(usage_data, execution_id=execution_id)
|
87
|
-
|
88
|
-
|
89
|
-
class AsyncOpenAIWrapper(BaseOpenAIWrapper):
|
90
|
-
async def create(self, *args: Any, execution_id: Optional[str] = None, **kwargs: Any) -> Union[ChatCompletion, AsyncIterator[ChatCompletion]]:
|
91
|
-
"""Create a chat completion and log token usage."""
|
92
|
-
logger.debug("Creating chat completion with args: %s, kwargs: %s", args, kwargs)
|
93
|
-
|
94
|
-
if kwargs.get('stream', False):
|
95
|
-
response = await self.client.chat.completions.create(*args, **kwargs)
|
96
|
-
return self._wrap_streaming_response(response, execution_id)
|
97
|
-
|
98
|
-
response = await self.client.chat.completions.create(*args, **kwargs)
|
99
|
-
usage_data = self._process_response_usage(response)
|
100
|
-
if usage_data:
|
101
|
-
self._log_usage(usage_data, execution_id=execution_id)
|
102
|
-
return response
|
103
|
-
|
104
|
-
async def _wrap_streaming_response(self, response_iter: AsyncStream[ChatCompletionChunk], execution_id: Optional[str]) -> AsyncIterator[ChatCompletionChunk]:
|
105
|
-
"""Wrap streaming response to capture final usage stats"""
|
106
|
-
chunks_with_usage = []
|
107
|
-
async for chunk in response_iter:
|
108
|
-
if isinstance(chunk, ChatCompletionChunk) and chunk.usage is not None:
|
109
|
-
chunks_with_usage.append(chunk)
|
110
|
-
yield chunk
|
111
|
-
|
112
|
-
if len(chunks_with_usage) > 0:
|
113
|
-
usage_data: TokenUsageStats = TokenUsageStats(model=chunks_with_usage[0].model, usage=Usage())
|
114
|
-
for chunk in chunks_with_usage:
|
115
|
-
usage_data.usage.prompt_tokens += chunk.usage.prompt_tokens
|
116
|
-
usage_data.usage.completion_tokens += chunk.usage.completion_tokens
|
117
|
-
usage_data.usage.total_tokens += chunk.usage.total_tokens
|
118
|
-
|
119
|
-
self._log_usage(usage_data, execution_id=execution_id)
|
120
|
-
|
121
|
-
@overload
|
122
|
-
def tokenator_openai(
|
123
|
-
client: OpenAI,
|
124
|
-
db_path: Optional[str] = None,
|
125
|
-
) -> OpenAIWrapper: ...
|
126
|
-
|
127
|
-
@overload
|
128
|
-
def tokenator_openai(
|
129
|
-
client: AsyncOpenAI,
|
130
|
-
db_path: Optional[str] = None,
|
131
|
-
) -> AsyncOpenAIWrapper: ...
|
132
|
-
|
133
|
-
def tokenator_openai(
|
134
|
-
client: Union[OpenAI, AsyncOpenAI],
|
135
|
-
db_path: Optional[str] = None,
|
136
|
-
) -> Union[OpenAIWrapper, AsyncOpenAIWrapper]:
|
137
|
-
"""Create a token-tracking wrapper for an OpenAI client.
|
138
|
-
|
139
|
-
Args:
|
140
|
-
client: OpenAI or AsyncOpenAI client instance
|
141
|
-
db_path: Optional path to SQLite database for token tracking
|
142
|
-
"""
|
143
|
-
if isinstance(client, OpenAI):
|
144
|
-
return OpenAIWrapper(client=client, db_path=db_path)
|
145
|
-
|
146
|
-
if isinstance(client, AsyncOpenAI):
|
147
|
-
return AsyncOpenAIWrapper(client=client, db_path=db_path)
|
148
|
-
|
149
|
-
raise ValueError("Client must be an instance of OpenAI or AsyncOpenAI")
|
150
|
-
|
151
|
-
__all__ = ["tokenator_openai"]
|
tokenator-0.1.8.dist-info/RECORD
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
tokenator/__init__.py,sha256=ZKe0zMGa_AqOeXUVgYqivUavht_byk03XNFEvAnxqsA,576
|
2
|
-
tokenator/base_wrapper.py,sha256=vSu_pStKYulho7_5g0jMCNf84KRxC4kTKep0v8YE61M,2377
|
3
|
-
tokenator/client_anthropic.py,sha256=1ejWIZBxtk-mWTVaKWeMUvS2hZ_Dn-vNKYa3yopdjAU,6714
|
4
|
-
tokenator/client_openai.py,sha256=_4jvchKzpCFhpioMZTYIWV7_ephQp1abMCtswUDJv1M,6339
|
5
|
-
tokenator/create_migrations.py,sha256=n1OVbWrdwvBdaN-Aqqt1gLCPQidfoQfeJtGsab_epGk,746
|
6
|
-
tokenator/migrations/env.py,sha256=LR_hONDa8Saiq9CyNUpH8kZCi5PtXLaDlfABs_CePkk,1415
|
7
|
-
tokenator/migrations/script.py.mako,sha256=nJL-tbLQE0Qy4P9S4r4ntNAcikPtoFUlvXe6xvm9ot8,635
|
8
|
-
tokenator/migrations/versions/f6f1f2437513_initial_migration.py,sha256=DvHcjnREmUHZVX9q1e6PS4wNK_d4qGw-8pz0eS4_3mE,1860
|
9
|
-
tokenator/migrations.py,sha256=BFgZRsdIx-Qs_WwDaH6cyi2124mLf5hA8VrIlW7f7Mg,1134
|
10
|
-
tokenator/models.py,sha256=EprE_MMJxDS-YXlcIQLZzfekH7xTYbeOC3bx3B2osVw,1171
|
11
|
-
tokenator/schemas.py,sha256=V7NYfY9eZvH3J6uOwXJz4dSAU6WYzINRnfFi1wWsTcc,2280
|
12
|
-
tokenator/usage.py,sha256=aHjGwzDzaiVznahNk5HqVyk3IxDo5FtFVfOUCeE7DZ4,7833
|
13
|
-
tokenator/utils.py,sha256=5mDiGHgt4koCY0onHwkRjwZIuAgP6QvrDZCwD20Sdk8,1969
|
14
|
-
tokenator-0.1.8.dist-info/LICENSE,sha256=wdG-B6-ODk8RQ4jq5uXSn0w1UWTzCH_MMyvh7AwtGns,1074
|
15
|
-
tokenator-0.1.8.dist-info/METADATA,sha256=1xgNdiPKTJlnBCPH6iMfi7-LoOl-t9soFzH_5V_eYIk,2444
|
16
|
-
tokenator-0.1.8.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
17
|
-
tokenator-0.1.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|