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
@@ -28,27 +49,41 @@ class Valves(BaseModel):
28
49
api_endpoint : str = Field (default = "" , description = "openwebui-monitor's base url" )
29
50
api_key : str = Field (default = "" , description = "openwebui-monitor's api key" )
30
51
priority : int = Field (default = 5 , description = "filter priority" )
52
+ language : str = Field (default = "zh" , description = "language (en/zh)" )
53
+ show_time_spent : bool = Field (default = True , description = "show time spent" )
54
+ show_tokens_per_sec : bool = Field (default = True , description = "show tokens per second" )
55
+ show_cost : bool = Field (default = True , description = "show cost" )
56
+ show_balance : bool = Field (default = True , description = "show balance" )
57
+ show_tokens : bool = Field (default = True , description = "show tokens" )
31
58
32
59
def __init__ (self ):
33
60
self .type = "filter"
61
+ self .name = "OpenWebUI Monitor"
34
62
self .valves = self .Valves ()
35
63
self .outage_map : Dict [str , bool ] = {}
36
-
64
+ self .start_time : Optional [float ] = None
65
+
66
+ def get_text (self , key : str , ** kwargs ) -> str :
67
+ lang = self .valves .language if self .valves .language in TRANSLATIONS else "en"
68
+ text = TRANSLATIONS [lang ].get (key , TRANSLATIONS ["en" ][key ])
69
+ return text .format (** kwargs ) if kwargs else text
70
+
37
71
async def request (self , client : AsyncClient , url : str , headers : dict , json_data : dict ):
38
72
json_data = json .loads (json .dumps (json_data , default = lambda o : o .dict () if hasattr (o , "dict" ) else str (o )))
39
73
40
74
response = await client .post (url = url , headers = headers , json = json_data )
41
75
response .raise_for_status ()
42
76
response_data = response .json ()
43
77
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" )
78
+ logger .error (self . get_text ( "request_failed " , error_msg = response_data ) )
79
+ raise CustomException (self . get_text ( "request_failed" , error_msg = response_data ) )
46
80
return response_data
47
81
48
82
async def inlet (self , body : dict , __metadata__ : Optional [dict ] = None , __user__ : Optional [dict ] = None ) -> dict :
49
83
__user__ = __user__ or {}
50
84
__metadata__ = __metadata__ or {}
51
- user_id = __user__ ["id" ]
85
+ self .start_time = time .time ()
86
+ user_id = __user__ .get ("id" , "default" )
52
87
53
88
client = AsyncClient ()
54
89
@@ -61,13 +96,12 @@ async def inlet(self, body: dict, __metadata__: Optional[dict] = None, __user__:
61
96
)
62
97
self .outage_map [user_id ] = response_data .get ("balance" , 0 ) <= 0
63
98
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
-
99
+ logger .info (self .get_text ("insufficient_balance" , balance = response_data .get ("balance" , 0 )))
100
+ raise CustomException (self .get_text ("insufficient_balance" , balance = response_data .get ("balance" , 0 )))
67
101
return body
68
102
69
103
except Exception as err :
70
- logger .exception ("[usage_monitor] error calculating usage: %s " , err )
104
+ logger .exception (self . get_text ( "request_failed " , error_msg = err ) )
71
105
if isinstance (err , CustomException ):
72
106
raise err
73
107
raise Exception (f"error calculating usage, { err } " ) from err
@@ -80,13 +114,13 @@ async def outlet(
80
114
body : dict ,
81
115
__metadata__ : Optional [dict ] = None ,
82
116
__user__ : Optional [dict ] = None ,
83
- __event_emitter__ : callable = None ,
117
+ __event_emitter__ : Optional [ callable ] = None ,
84
118
) -> dict :
85
119
__user__ = __user__ or {}
86
120
__metadata__ = __metadata__ or {}
87
- user_id = __user__ [ "id" ]
121
+ user_id = __user__ . get ( "id" , "default" )
88
122
89
- if self .outage_map [ user_id ] :
123
+ if self .outage_map . get ( user_id , False ) :
90
124
return body
91
125
92
126
client = AsyncClient ()
@@ -99,23 +133,29 @@ async def outlet(
99
133
json_data = {"user" : __user__ , "body" : body },
100
134
)
101
135
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 }})
136
+ stats_list = []
137
+ if self .valves .show_tokens :
138
+ stats_list .append (self .get_text ("tokens" , input = response_data ["inputTokens" ], output = response_data ["outputTokens" ]))
139
+ if self .valves .show_cost :
140
+ stats_list .append (self .get_text ("cost" , cost = response_data ["totalCost" ]))
141
+ if self .valves .show_balance :
142
+ stats_list .append (self .get_text ("balance" , balance = response_data ["newBalance" ]))
143
+ if self .start_time and self .valves .show_time_spent :
144
+ elapsed = time .time () - self .start_time
145
+ stats_list .append (self .get_text ("time_spent" , time = elapsed ))
146
+ if self .valves .show_tokens_per_sec :
147
+ tokens_per_sec = (response_data ["outputTokens" ] / elapsed if elapsed > 0 else 0 )
148
+ stats_list .append (self .get_text ("tokens_per_sec" , tokens_per_sec = tokens_per_sec ))
149
+
150
+ stats = " | " .join (stats_list )
151
+ if __event_emitter__ :
152
+ await __event_emitter__ ({"type" : "status" , "data" : {"description" : stats , "done" : True }})
112
153
113
154
logger .info ("usage_monitor: %s %s" , user_id , stats )
114
155
return body
115
156
116
157
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
-
158
+ logger .exception (self .get_text ("request_failed" , error_msg = err ))
159
+ raise Exception (self .get_text ("request_failed" , error_msg = err ))
120
160
finally :
121
161
await client .aclose ()
0 commit comments