statistic.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. from datetime import datetime
  2. from decimal import Decimal
  3. import pytz
  4. from flask import jsonify
  5. from flask_login import current_user
  6. from flask_restful import Resource, reqparse
  7. from controllers.console import api
  8. from controllers.console.app.wraps import get_app_model
  9. from controllers.console.setup import setup_required
  10. from controllers.console.wraps import account_initialization_required
  11. from extensions.ext_database import db
  12. from libs.helper import DatetimeString
  13. from libs.login import login_required
  14. from models.model import AppMode
  15. class DailyMessageStatistic(Resource):
  16. @setup_required
  17. @login_required
  18. @account_initialization_required
  19. @get_app_model
  20. def get(self, app_model):
  21. account = current_user
  22. parser = reqparse.RequestParser()
  23. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  24. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  25. args = parser.parse_args()
  26. sql_query = """SELECT
  27. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  28. COUNT(*) AS message_count
  29. FROM
  30. messages
  31. WHERE
  32. app_id = :app_id"""
  33. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  34. timezone = pytz.timezone(account.timezone)
  35. utc_timezone = pytz.utc
  36. if args["start"]:
  37. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  38. start_datetime = start_datetime.replace(second=0)
  39. start_datetime_timezone = timezone.localize(start_datetime)
  40. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  41. sql_query += " AND created_at >= :start"
  42. arg_dict["start"] = start_datetime_utc
  43. if args["end"]:
  44. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  45. end_datetime = end_datetime.replace(second=0)
  46. end_datetime_timezone = timezone.localize(end_datetime)
  47. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  48. sql_query += " AND created_at < :end"
  49. arg_dict["end"] = end_datetime_utc
  50. sql_query += " GROUP BY date ORDER BY date"
  51. response_data = []
  52. with db.engine.begin() as conn:
  53. rs = conn.execute(db.text(sql_query), arg_dict)
  54. for i in rs:
  55. response_data.append({"date": str(i.date), "message_count": i.message_count})
  56. return jsonify({"data": response_data})
  57. class DailyConversationStatistic(Resource):
  58. @setup_required
  59. @login_required
  60. @account_initialization_required
  61. @get_app_model
  62. def get(self, app_model):
  63. account = current_user
  64. parser = reqparse.RequestParser()
  65. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  66. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  67. args = parser.parse_args()
  68. sql_query = """SELECT
  69. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  70. COUNT(DISTINCT messages.conversation_id) AS conversation_count
  71. FROM
  72. messages
  73. WHERE
  74. app_id = :app_id"""
  75. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  76. timezone = pytz.timezone(account.timezone)
  77. utc_timezone = pytz.utc
  78. if args["start"]:
  79. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  80. start_datetime = start_datetime.replace(second=0)
  81. start_datetime_timezone = timezone.localize(start_datetime)
  82. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  83. sql_query += " AND created_at >= :start"
  84. arg_dict["start"] = start_datetime_utc
  85. if args["end"]:
  86. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  87. end_datetime = end_datetime.replace(second=0)
  88. end_datetime_timezone = timezone.localize(end_datetime)
  89. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  90. sql_query += " AND created_at < :end"
  91. arg_dict["end"] = end_datetime_utc
  92. sql_query += " GROUP BY date ORDER BY date"
  93. response_data = []
  94. with db.engine.begin() as conn:
  95. rs = conn.execute(db.text(sql_query), arg_dict)
  96. for i in rs:
  97. response_data.append({"date": str(i.date), "conversation_count": i.conversation_count})
  98. return jsonify({"data": response_data})
  99. class DailyTerminalsStatistic(Resource):
  100. @setup_required
  101. @login_required
  102. @account_initialization_required
  103. @get_app_model
  104. def get(self, app_model):
  105. account = current_user
  106. parser = reqparse.RequestParser()
  107. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  108. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  109. args = parser.parse_args()
  110. sql_query = """SELECT
  111. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  112. COUNT(DISTINCT messages.from_end_user_id) AS terminal_count
  113. FROM
  114. messages
  115. WHERE
  116. app_id = :app_id"""
  117. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  118. timezone = pytz.timezone(account.timezone)
  119. utc_timezone = pytz.utc
  120. if args["start"]:
  121. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  122. start_datetime = start_datetime.replace(second=0)
  123. start_datetime_timezone = timezone.localize(start_datetime)
  124. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  125. sql_query += " AND created_at >= :start"
  126. arg_dict["start"] = start_datetime_utc
  127. if args["end"]:
  128. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  129. end_datetime = end_datetime.replace(second=0)
  130. end_datetime_timezone = timezone.localize(end_datetime)
  131. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  132. sql_query += " AND created_at < :end"
  133. arg_dict["end"] = end_datetime_utc
  134. sql_query += " GROUP BY date ORDER BY date"
  135. response_data = []
  136. with db.engine.begin() as conn:
  137. rs = conn.execute(db.text(sql_query), arg_dict)
  138. for i in rs:
  139. response_data.append({"date": str(i.date), "terminal_count": i.terminal_count})
  140. return jsonify({"data": response_data})
  141. class DailyTokenCostStatistic(Resource):
  142. @setup_required
  143. @login_required
  144. @account_initialization_required
  145. @get_app_model
  146. def get(self, app_model):
  147. account = current_user
  148. parser = reqparse.RequestParser()
  149. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  150. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  151. args = parser.parse_args()
  152. sql_query = """SELECT
  153. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  154. (SUM(messages.message_tokens) + SUM(messages.answer_tokens)) AS token_count,
  155. SUM(total_price) AS total_price
  156. FROM
  157. messages
  158. WHERE
  159. app_id = :app_id"""
  160. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  161. timezone = pytz.timezone(account.timezone)
  162. utc_timezone = pytz.utc
  163. if args["start"]:
  164. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  165. start_datetime = start_datetime.replace(second=0)
  166. start_datetime_timezone = timezone.localize(start_datetime)
  167. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  168. sql_query += " AND created_at >= :start"
  169. arg_dict["start"] = start_datetime_utc
  170. if args["end"]:
  171. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  172. end_datetime = end_datetime.replace(second=0)
  173. end_datetime_timezone = timezone.localize(end_datetime)
  174. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  175. sql_query += " AND created_at < :end"
  176. arg_dict["end"] = end_datetime_utc
  177. sql_query += " GROUP BY date ORDER BY date"
  178. response_data = []
  179. with db.engine.begin() as conn:
  180. rs = conn.execute(db.text(sql_query), arg_dict)
  181. for i in rs:
  182. response_data.append(
  183. {"date": str(i.date), "token_count": i.token_count, "total_price": i.total_price, "currency": "USD"}
  184. )
  185. return jsonify({"data": response_data})
  186. class AverageSessionInteractionStatistic(Resource):
  187. @setup_required
  188. @login_required
  189. @account_initialization_required
  190. @get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
  191. def get(self, app_model):
  192. account = current_user
  193. parser = reqparse.RequestParser()
  194. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  195. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  196. args = parser.parse_args()
  197. sql_query = """SELECT
  198. DATE(DATE_TRUNC('day', c.created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  199. AVG(subquery.message_count) AS interactions
  200. FROM
  201. (
  202. SELECT
  203. m.conversation_id,
  204. COUNT(m.id) AS message_count
  205. FROM
  206. conversations c
  207. JOIN
  208. messages m
  209. ON c.id = m.conversation_id
  210. WHERE
  211. c.override_model_configs IS NULL
  212. AND c.app_id = :app_id"""
  213. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  214. timezone = pytz.timezone(account.timezone)
  215. utc_timezone = pytz.utc
  216. if args["start"]:
  217. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  218. start_datetime = start_datetime.replace(second=0)
  219. start_datetime_timezone = timezone.localize(start_datetime)
  220. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  221. sql_query += " AND c.created_at >= :start"
  222. arg_dict["start"] = start_datetime_utc
  223. if args["end"]:
  224. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  225. end_datetime = end_datetime.replace(second=0)
  226. end_datetime_timezone = timezone.localize(end_datetime)
  227. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  228. sql_query += " AND c.created_at < :end"
  229. arg_dict["end"] = end_datetime_utc
  230. sql_query += """
  231. GROUP BY m.conversation_id
  232. ) subquery
  233. LEFT JOIN
  234. conversations c
  235. ON c.id = subquery.conversation_id
  236. GROUP BY
  237. date
  238. ORDER BY
  239. date"""
  240. response_data = []
  241. with db.engine.begin() as conn:
  242. rs = conn.execute(db.text(sql_query), arg_dict)
  243. for i in rs:
  244. response_data.append(
  245. {"date": str(i.date), "interactions": float(i.interactions.quantize(Decimal("0.01")))}
  246. )
  247. return jsonify({"data": response_data})
  248. class UserSatisfactionRateStatistic(Resource):
  249. @setup_required
  250. @login_required
  251. @account_initialization_required
  252. @get_app_model
  253. def get(self, app_model):
  254. account = current_user
  255. parser = reqparse.RequestParser()
  256. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  257. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  258. args = parser.parse_args()
  259. sql_query = """SELECT
  260. DATE(DATE_TRUNC('day', m.created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  261. COUNT(m.id) AS message_count,
  262. COUNT(mf.id) AS feedback_count
  263. FROM
  264. messages m
  265. LEFT JOIN
  266. message_feedbacks mf
  267. ON mf.message_id=m.id AND mf.rating='like'
  268. WHERE
  269. m.app_id = :app_id"""
  270. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  271. timezone = pytz.timezone(account.timezone)
  272. utc_timezone = pytz.utc
  273. if args["start"]:
  274. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  275. start_datetime = start_datetime.replace(second=0)
  276. start_datetime_timezone = timezone.localize(start_datetime)
  277. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  278. sql_query += " AND m.created_at >= :start"
  279. arg_dict["start"] = start_datetime_utc
  280. if args["end"]:
  281. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  282. end_datetime = end_datetime.replace(second=0)
  283. end_datetime_timezone = timezone.localize(end_datetime)
  284. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  285. sql_query += " AND m.created_at < :end"
  286. arg_dict["end"] = end_datetime_utc
  287. sql_query += " GROUP BY date ORDER BY date"
  288. response_data = []
  289. with db.engine.begin() as conn:
  290. rs = conn.execute(db.text(sql_query), arg_dict)
  291. for i in rs:
  292. response_data.append(
  293. {
  294. "date": str(i.date),
  295. "rate": round((i.feedback_count * 1000 / i.message_count) if i.message_count > 0 else 0, 2),
  296. }
  297. )
  298. return jsonify({"data": response_data})
  299. class AverageResponseTimeStatistic(Resource):
  300. @setup_required
  301. @login_required
  302. @account_initialization_required
  303. @get_app_model(mode=AppMode.COMPLETION)
  304. def get(self, app_model):
  305. account = current_user
  306. parser = reqparse.RequestParser()
  307. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  308. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  309. args = parser.parse_args()
  310. sql_query = """SELECT
  311. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  312. AVG(provider_response_latency) AS latency
  313. FROM
  314. messages
  315. WHERE
  316. app_id = :app_id"""
  317. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  318. timezone = pytz.timezone(account.timezone)
  319. utc_timezone = pytz.utc
  320. if args["start"]:
  321. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  322. start_datetime = start_datetime.replace(second=0)
  323. start_datetime_timezone = timezone.localize(start_datetime)
  324. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  325. sql_query += " AND created_at >= :start"
  326. arg_dict["start"] = start_datetime_utc
  327. if args["end"]:
  328. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  329. end_datetime = end_datetime.replace(second=0)
  330. end_datetime_timezone = timezone.localize(end_datetime)
  331. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  332. sql_query += " AND created_at < :end"
  333. arg_dict["end"] = end_datetime_utc
  334. sql_query += " GROUP BY date ORDER BY date"
  335. response_data = []
  336. with db.engine.begin() as conn:
  337. rs = conn.execute(db.text(sql_query), arg_dict)
  338. for i in rs:
  339. response_data.append({"date": str(i.date), "latency": round(i.latency * 1000, 4)})
  340. return jsonify({"data": response_data})
  341. class TokensPerSecondStatistic(Resource):
  342. @setup_required
  343. @login_required
  344. @account_initialization_required
  345. @get_app_model
  346. def get(self, app_model):
  347. account = current_user
  348. parser = reqparse.RequestParser()
  349. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  350. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  351. args = parser.parse_args()
  352. sql_query = """SELECT
  353. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  354. CASE
  355. WHEN SUM(provider_response_latency) = 0 THEN 0
  356. ELSE (SUM(answer_tokens) / SUM(provider_response_latency))
  357. END as tokens_per_second
  358. FROM
  359. messages
  360. WHERE
  361. app_id = :app_id"""
  362. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  363. timezone = pytz.timezone(account.timezone)
  364. utc_timezone = pytz.utc
  365. if args["start"]:
  366. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  367. start_datetime = start_datetime.replace(second=0)
  368. start_datetime_timezone = timezone.localize(start_datetime)
  369. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  370. sql_query += " AND created_at >= :start"
  371. arg_dict["start"] = start_datetime_utc
  372. if args["end"]:
  373. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  374. end_datetime = end_datetime.replace(second=0)
  375. end_datetime_timezone = timezone.localize(end_datetime)
  376. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  377. sql_query += " AND created_at < :end"
  378. arg_dict["end"] = end_datetime_utc
  379. sql_query += " GROUP BY date ORDER BY date"
  380. response_data = []
  381. with db.engine.begin() as conn:
  382. rs = conn.execute(db.text(sql_query), arg_dict)
  383. for i in rs:
  384. response_data.append({"date": str(i.date), "tps": round(i.tokens_per_second, 4)})
  385. return jsonify({"data": response_data})
  386. api.add_resource(DailyMessageStatistic, "/apps/<uuid:app_id>/statistics/daily-messages")
  387. api.add_resource(DailyConversationStatistic, "/apps/<uuid:app_id>/statistics/daily-conversations")
  388. api.add_resource(DailyTerminalsStatistic, "/apps/<uuid:app_id>/statistics/daily-end-users")
  389. api.add_resource(DailyTokenCostStatistic, "/apps/<uuid:app_id>/statistics/token-costs")
  390. api.add_resource(AverageSessionInteractionStatistic, "/apps/<uuid:app_id>/statistics/average-session-interactions")
  391. api.add_resource(UserSatisfactionRateStatistic, "/apps/<uuid:app_id>/statistics/user-satisfaction-rate")
  392. api.add_resource(AverageResponseTimeStatistic, "/apps/<uuid:app_id>/statistics/average-response-time")
  393. api.add_resource(TokensPerSecondStatistic, "/apps/<uuid:app_id>/statistics/tokens-per-second")