Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

statistic.py 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. from datetime import datetime
  2. from decimal import Decimal
  3. import pytz
  4. import sqlalchemy as sa
  5. from flask import jsonify
  6. from flask_login import current_user
  7. from flask_restful import Resource, reqparse
  8. from controllers.console import api
  9. from controllers.console.app.wraps import get_app_model
  10. from controllers.console.wraps import account_initialization_required, setup_required
  11. from core.app.entities.app_invoke_entities import InvokeFrom
  12. from extensions.ext_database import db
  13. from libs.helper import DatetimeString
  14. from libs.login import login_required
  15. from models import AppMode, Message
  16. class DailyMessageStatistic(Resource):
  17. @setup_required
  18. @login_required
  19. @account_initialization_required
  20. @get_app_model
  21. def get(self, app_model):
  22. account = current_user
  23. parser = reqparse.RequestParser()
  24. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  25. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  26. args = parser.parse_args()
  27. sql_query = """SELECT
  28. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  29. COUNT(*) AS message_count
  30. FROM
  31. messages
  32. WHERE
  33. app_id = :app_id"""
  34. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  35. timezone = pytz.timezone(account.timezone)
  36. utc_timezone = pytz.utc
  37. if args["start"]:
  38. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  39. start_datetime = start_datetime.replace(second=0)
  40. start_datetime_timezone = timezone.localize(start_datetime)
  41. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  42. sql_query += " AND created_at >= :start"
  43. arg_dict["start"] = start_datetime_utc
  44. if args["end"]:
  45. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  46. end_datetime = end_datetime.replace(second=0)
  47. end_datetime_timezone = timezone.localize(end_datetime)
  48. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  49. sql_query += " AND created_at < :end"
  50. arg_dict["end"] = end_datetime_utc
  51. sql_query += " GROUP BY date ORDER BY date"
  52. response_data = []
  53. with db.engine.begin() as conn:
  54. rs = conn.execute(db.text(sql_query), arg_dict)
  55. for i in rs:
  56. response_data.append({"date": str(i.date), "message_count": i.message_count})
  57. return jsonify({"data": response_data})
  58. class DailyConversationStatistic(Resource):
  59. @setup_required
  60. @login_required
  61. @account_initialization_required
  62. @get_app_model
  63. def get(self, app_model):
  64. account = current_user
  65. parser = reqparse.RequestParser()
  66. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  67. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  68. args = parser.parse_args()
  69. timezone = pytz.timezone(account.timezone)
  70. utc_timezone = pytz.utc
  71. stmt = (
  72. sa.select(
  73. sa.func.date(
  74. sa.func.date_trunc("day", sa.text("created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz"))
  75. ).label("date"),
  76. sa.func.count(sa.distinct(Message.conversation_id)).label("conversation_count"),
  77. )
  78. .select_from(Message)
  79. .where(Message.app_id == app_model.id, Message.invoke_from != InvokeFrom.DEBUGGER.value)
  80. )
  81. if args["start"]:
  82. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  83. start_datetime = start_datetime.replace(second=0)
  84. start_datetime_timezone = timezone.localize(start_datetime)
  85. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  86. stmt = stmt.where(Message.created_at >= start_datetime_utc)
  87. if args["end"]:
  88. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  89. end_datetime = end_datetime.replace(second=0)
  90. end_datetime_timezone = timezone.localize(end_datetime)
  91. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  92. stmt = stmt.where(Message.created_at < end_datetime_utc)
  93. stmt = stmt.group_by("date").order_by("date")
  94. response_data = []
  95. with db.engine.begin() as conn:
  96. rs = conn.execute(stmt, {"tz": account.timezone})
  97. for row in rs:
  98. response_data.append({"date": str(row.date), "conversation_count": row.conversation_count})
  99. return jsonify({"data": response_data})
  100. class DailyTerminalsStatistic(Resource):
  101. @setup_required
  102. @login_required
  103. @account_initialization_required
  104. @get_app_model
  105. def get(self, app_model):
  106. account = current_user
  107. parser = reqparse.RequestParser()
  108. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  109. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  110. args = parser.parse_args()
  111. sql_query = """SELECT
  112. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  113. COUNT(DISTINCT messages.from_end_user_id) AS terminal_count
  114. FROM
  115. messages
  116. WHERE
  117. app_id = :app_id"""
  118. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  119. timezone = pytz.timezone(account.timezone)
  120. utc_timezone = pytz.utc
  121. if args["start"]:
  122. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  123. start_datetime = start_datetime.replace(second=0)
  124. start_datetime_timezone = timezone.localize(start_datetime)
  125. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  126. sql_query += " AND created_at >= :start"
  127. arg_dict["start"] = start_datetime_utc
  128. if args["end"]:
  129. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  130. end_datetime = end_datetime.replace(second=0)
  131. end_datetime_timezone = timezone.localize(end_datetime)
  132. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  133. sql_query += " AND created_at < :end"
  134. arg_dict["end"] = end_datetime_utc
  135. sql_query += " GROUP BY date ORDER BY date"
  136. response_data = []
  137. with db.engine.begin() as conn:
  138. rs = conn.execute(db.text(sql_query), arg_dict)
  139. for i in rs:
  140. response_data.append({"date": str(i.date), "terminal_count": i.terminal_count})
  141. return jsonify({"data": response_data})
  142. class DailyTokenCostStatistic(Resource):
  143. @setup_required
  144. @login_required
  145. @account_initialization_required
  146. @get_app_model
  147. def get(self, app_model):
  148. account = current_user
  149. parser = reqparse.RequestParser()
  150. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  151. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  152. args = parser.parse_args()
  153. sql_query = """SELECT
  154. DATE(DATE_TRUNC('day', created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  155. (SUM(messages.message_tokens) + SUM(messages.answer_tokens)) AS token_count,
  156. SUM(total_price) AS total_price
  157. FROM
  158. messages
  159. WHERE
  160. app_id = :app_id"""
  161. arg_dict = {"tz": account.timezone, "app_id": app_model.id}
  162. timezone = pytz.timezone(account.timezone)
  163. utc_timezone = pytz.utc
  164. if args["start"]:
  165. start_datetime = datetime.strptime(args["start"], "%Y-%m-%d %H:%M")
  166. start_datetime = start_datetime.replace(second=0)
  167. start_datetime_timezone = timezone.localize(start_datetime)
  168. start_datetime_utc = start_datetime_timezone.astimezone(utc_timezone)
  169. sql_query += " AND created_at >= :start"
  170. arg_dict["start"] = start_datetime_utc
  171. if args["end"]:
  172. end_datetime = datetime.strptime(args["end"], "%Y-%m-%d %H:%M")
  173. end_datetime = end_datetime.replace(second=0)
  174. end_datetime_timezone = timezone.localize(end_datetime)
  175. end_datetime_utc = end_datetime_timezone.astimezone(utc_timezone)
  176. sql_query += " AND created_at < :end"
  177. arg_dict["end"] = end_datetime_utc
  178. sql_query += " GROUP BY date ORDER BY date"
  179. response_data = []
  180. with db.engine.begin() as conn:
  181. rs = conn.execute(db.text(sql_query), arg_dict)
  182. for i in rs:
  183. response_data.append(
  184. {"date": str(i.date), "token_count": i.token_count, "total_price": i.total_price, "currency": "USD"}
  185. )
  186. return jsonify({"data": response_data})
  187. class AverageSessionInteractionStatistic(Resource):
  188. @setup_required
  189. @login_required
  190. @account_initialization_required
  191. @get_app_model(mode=[AppMode.CHAT, AppMode.AGENT_CHAT, AppMode.ADVANCED_CHAT])
  192. def get(self, app_model):
  193. account = current_user
  194. parser = reqparse.RequestParser()
  195. parser.add_argument("start", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  196. parser.add_argument("end", type=DatetimeString("%Y-%m-%d %H:%M"), location="args")
  197. args = parser.parse_args()
  198. sql_query = """SELECT
  199. DATE(DATE_TRUNC('day', c.created_at AT TIME ZONE 'UTC' AT TIME ZONE :tz )) AS date,
  200. AVG(subquery.message_count) AS interactions
  201. FROM
  202. (
  203. SELECT
  204. m.conversation_id,
  205. COUNT(m.id) AS message_count
  206. FROM
  207. conversations c
  208. JOIN
  209. messages m
  210. ON c.id = m.conversation_id
  211. WHERE
  212. 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")