8
8
"""
9
9
10
10
import logging
11
+ import time
11
12
from typing import Dict , Optional
12
-
13
13
from httpx import AsyncClient
14
14
from pydantic import BaseModel , Field
15
15
import json
18
18
logger = logging .getLogger (__name__ )
19
19
logger .setLevel (logging .INFO )
20
20
21
+ TRANSLATIONS = {
22
+ "en" : {
23
+ "request_failed" : "Request failed: {error_msg}" ,
24
+ "insufficient_balance" : "Insufficient balance: Current balance `{balance:.4f}`" ,
25
+ "cost" : "Cost: ${cost:.4f}" ,
26
+ "balance" : "Balance: ${balance:.4f}" ,
27
+ "tokens" : "Tokens: {input}+{output}" ,
28
+ "time_spent" : "Time: {time:.2f}s" ,
29
+ "tokens_per_sec" : "{tokens_per_sec:.2f} T/s" ,
30
+ },
31
+ "zh" : {
32
+ "request_failed" : "请求失败: {error_msg}" ,
33
+ "insufficient_balance" : "余额不足: 当前余额 `{balance:.4f}`" ,
34
+ "cost" : "费用: ¥{cost:.4f}" ,
35
+ "balance" : "余额: ¥{balance:.4f}" ,
36
+ "tokens" : "Token: {input}+{output}" ,
37
+ "time_spent" : "耗时: {time:.2f}s" ,
38
+ "tokens_per_sec" : "{tokens_per_sec:.2f} T/s" ,
39
+ },
40
+ }
41
+
21
42
22
43
class CustomException (Exception ):
23
44
pass
24
45
25
46
26
47
class Filter :
27
48
class Valves (BaseModel ):
28
- api_endpoint : str = Field (default = "" , description = "openwebui-monitor's base url" )
49
+ api_endpoint : str = Field (
50
+ default = "" , description = "openwebui-monitor's base url"
51
+ )
29
52
api_key : str = Field (default = "" , description = "openwebui-monitor's api key" )
30
53
priority : int = Field (default = 5 , description = "filter priority" )
54
+ language : str = Field (default = "zh" , description = "language (en/zh)" )
55
+ show_time_spent : bool = Field (default = True , description = "show time spent" )
56
+ show_tokens_per_sec : bool = Field (
57
+ default = True , description = "show tokens per second"
58
+ )
59
+ show_cost : bool = Field (default = True , description = "show cost" )
60
+ show_balance : bool = Field (default = True , description = "show balance" )
61
+ show_tokens : bool = Field (default = True , description = "show tokens" )
31
62
32
63
def __init__ (self ):
33
64
self .type = "filter"
65
+ self .name = "OpenWebUI Monitor"
34
66
self .valves = self .Valves ()
35
67
self .outage_map : Dict [str , bool ] = {}
36
-
37
- async def request (self , client : AsyncClient , url : str , headers : dict , json_data : dict ):
38
- json_data = json .loads (json .dumps (json_data , default = lambda o : o .dict () if hasattr (o , "dict" ) else str (o )))
39
-
68
+ self .start_time : Optional [float ] = None
69
+
70
+ def get_text (self , key : str , ** kwargs ) -> str :
71
+ lang = self .valves .language if self .valves .language in TRANSLATIONS else "en"
72
+ text = TRANSLATIONS [lang ].get (key , TRANSLATIONS ["en" ][key ])
73
+ return text .format (** kwargs ) if kwargs else text
74
+
75
+ async def request (
76
+ self , client : AsyncClient , url : str , headers : dict , json_data : dict
77
+ ):
78
+ json_data = json .loads (
79
+ json .dumps (
80
+ json_data , default = lambda o : o .dict () if hasattr (o , "dict" ) else str (o )
81
+ )
82
+ )
40
83
response = await client .post (url = url , headers = headers , json = json_data )
41
84
response .raise_for_status ()
42
85
response_data = response .json ()
43
86
if not response_data .get ("success" ):
44
- logger .error ("[usage_monitor] req monitor failed: %s" , response_data )
45
- raise CustomException ("calculate usage failed, please contact administrator" )
87
+ logger .error (self .get_text ("request_failed" , error_msg = response_data ))
88
+ raise CustomException (
89
+ self .get_text ("request_failed" , error_msg = response_data )
90
+ )
46
91
return response_data
47
92
48
- async def inlet (self , body : dict , __metadata__ : Optional [dict ] = None , __user__ : Optional [dict ] = None ) -> dict :
93
+ async def inlet (
94
+ self ,
95
+ body : dict ,
96
+ __metadata__ : Optional [dict ] = None ,
97
+ __user__ : Optional [dict ] = None ,
98
+ ) -> dict :
49
99
__user__ = __user__ or {}
50
100
__metadata__ = __metadata__ or {}
51
- user_id = __user__ ["id" ]
101
+ self .start_time = time .time ()
102
+ user_id = __user__ .get ("id" , "default" )
52
103
53
104
client = AsyncClient ()
54
-
55
105
try :
56
106
response_data = await self .request (
57
107
client = client ,
@@ -61,17 +111,22 @@ async def inlet(self, body: dict, __metadata__: Optional[dict] = None, __user__:
61
111
)
62
112
self .outage_map [user_id ] = response_data .get ("balance" , 0 ) <= 0
63
113
if self .outage_map [user_id ]:
64
- logger .info ("[usage_monitor] no balance: %s" , user_id )
65
- raise CustomException ("no balance, please contact administrator" )
66
-
114
+ logger .info (
115
+ self .get_text (
116
+ "insufficient_balance" , balance = response_data .get ("balance" , 0 )
117
+ )
118
+ )
119
+ raise CustomException (
120
+ self .get_text (
121
+ "insufficient_balance" , balance = response_data .get ("balance" , 0 )
122
+ )
123
+ )
67
124
return body
68
-
69
125
except Exception as err :
70
- logger .exception ("[usage_monitor] error calculating usage: %s " , err )
126
+ logger .exception (self . get_text ( "request_failed " , error_msg = err ) )
71
127
if isinstance (err , CustomException ):
72
128
raise err
73
129
raise Exception (f"error calculating usage, { err } " ) from err
74
-
75
130
finally :
76
131
await client .aclose ()
77
132
@@ -80,17 +135,16 @@ async def outlet(
80
135
body : dict ,
81
136
__metadata__ : Optional [dict ] = None ,
82
137
__user__ : Optional [dict ] = None ,
83
- __event_emitter__ : callable = None ,
138
+ __event_emitter__ : Optional [ callable ] = None ,
84
139
) -> dict :
85
140
__user__ = __user__ or {}
86
141
__metadata__ = __metadata__ or {}
87
- user_id = __user__ [ "id" ]
142
+ user_id = __user__ . get ( "id" , "default" )
88
143
89
- if self .outage_map [ user_id ] :
144
+ if self .outage_map . get ( user_id , False ) :
90
145
return body
91
146
92
147
client = AsyncClient ()
93
-
94
148
try :
95
149
response_data = await self .request (
96
150
client = client ,
@@ -99,23 +153,43 @@ async def outlet(
99
153
json_data = {"user" : __user__ , "body" : body },
100
154
)
101
155
102
- # pylint: disable=C0209
103
- stats = " | " .join (
104
- [
105
- f"Tokens: { response_data ['inputTokens' ]} + { response_data ['outputTokens' ]} " ,
106
- "Cost: %.4f" % response_data ["totalCost" ],
107
- "Balance: %.4f" % response_data ["newBalance" ],
108
- ]
109
- )
110
-
111
- await __event_emitter__ ({"type" : "status" , "data" : {"description" : stats , "done" : True }})
112
-
156
+ stats_list = []
157
+ if self .valves .show_tokens :
158
+ stats_list .append (
159
+ self .get_text (
160
+ "tokens" ,
161
+ input = response_data ["inputTokens" ],
162
+ output = response_data ["outputTokens" ],
163
+ )
164
+ )
165
+ if self .valves .show_cost :
166
+ stats_list .append (
167
+ self .get_text ("cost" , cost = response_data ["totalCost" ])
168
+ )
169
+ if self .valves .show_balance :
170
+ stats_list .append (
171
+ self .get_text ("balance" , balance = response_data ["newBalance" ])
172
+ )
173
+ if self .start_time and self .valves .show_time_spent :
174
+ elapsed = time .time () - self .start_time
175
+ stats_list .append (self .get_text ("time_spent" , time = elapsed ))
176
+ if self .valves .show_tokens_per_sec :
177
+ tokens_per_sec = (
178
+ response_data ["outputTokens" ] / elapsed if elapsed > 0 else 0
179
+ )
180
+ stats_list .append (
181
+ self .get_text ("tokens_per_sec" , tokens_per_sec = tokens_per_sec )
182
+ )
183
+
184
+ stats = " | " .join (stats_list )
185
+ if __event_emitter__ :
186
+ await __event_emitter__ (
187
+ {"type" : "status" , "data" : {"description" : stats , "done" : True }}
188
+ )
113
189
logger .info ("usage_monitor: %s %s" , user_id , stats )
114
190
return body
115
-
116
191
except Exception as err :
117
- logger .exception ("[usage_monitor] error calculating usage: %s" , err )
118
- raise Exception (f"error calculating usage, { err } " ) from err
119
-
192
+ logger .exception (self .get_text ("request_failed" , error_msg = err ))
193
+ raise Exception (self .get_text ("request_failed" , error_msg = err ))
120
194
finally :
121
195
await client .aclose ()
0 commit comments