88from sqlalchemy .orm import aliased
99
1010from apps .chat .models .chat_model import Chat , ChatRecord , CreateChat , ChatInfo , RenameChat , ChatQuestion , ChatLog , \
11- TypeEnum , OperationEnum , ChatRecordResult
11+ TypeEnum , OperationEnum , ChatRecordResult , ChatLogHistory , ChatLogHistoryItem
1212from apps .datasource .crud .recommended_problem import get_datasource_recommended_chart
1313from apps .datasource .models .datasource import CoreDatasource
1414from apps .db .constant import DB
@@ -345,11 +345,60 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
345345
346346 result = session .execute (stmt ).all ()
347347 record_list : list [ChatRecordResult ] = []
348+
349+ # 批量获取所有ChatRecord的token消耗
350+ record_ids = [row .id for row in result ]
351+ token_usage_map = {}
352+
353+ if record_ids :
354+ # 查询所有相关ChatLog的token_usage
355+ log_stmt = select (ChatLog .pid , ChatLog .token_usage ).where (
356+ and_ (
357+ ChatLog .pid .in_ (record_ids ),
358+ ChatLog .local_operation == False ,
359+ ChatLog .token_usage .is_not (None ) # 排除token_usage为空的记录
360+ )
361+ )
362+ log_results = session .execute (log_stmt ).all ()
363+
364+ # 按pid分组计算total_tokens总和
365+ for pid , token_usage in log_results :
366+ if pid and token_usage is not None :
367+ tokens_to_add = 0
368+
369+ if isinstance (token_usage , dict ):
370+ # 处理字典类型: {"input_tokens": 961, "total_tokens": 1006, "output_tokens": 45}
371+ if token_usage : # 非空字典
372+ if "total_tokens" in token_usage :
373+ token_value = token_usage ["total_tokens" ]
374+ if isinstance (token_value , (int , float )):
375+ tokens_to_add = int (token_value )
376+ elif isinstance (token_usage , (int , float )):
377+ tokens_to_add = int (token_usage )
378+ if tokens_to_add > 0 :
379+ if pid not in token_usage_map :
380+ token_usage_map [pid ] = 0
381+ token_usage_map [pid ] += tokens_to_add
382+
348383 for row in result :
384+ # 计算耗时
385+ duration = None
386+ if row .create_time and row .finish_time :
387+ try :
388+ time_diff = row .finish_time - row .create_time
389+ duration = time_diff .total_seconds () # 转换为秒
390+ except Exception :
391+ duration = None
392+
393+ # 获取token总消耗
394+ total_tokens = token_usage_map .get (row .id , 0 )
395+
349396 if not with_data :
350397 record_list .append (
351398 ChatRecordResult (id = row .id , chat_id = row .chat_id , create_time = row .create_time ,
352399 finish_time = row .finish_time ,
400+ duration = duration ,
401+ total_tokens = total_tokens ,
353402 question = row .question , sql_answer = row .sql_answer , sql = row .sql ,
354403 chart_answer = row .chart_answer , chart = row .chart ,
355404 analysis = row .analysis , predict = row .predict ,
@@ -367,6 +416,8 @@ def get_chat_with_records(session: SessionDep, chart_id: int, current_user: Curr
367416 record_list .append (
368417 ChatRecordResult (id = row .id , chat_id = row .chat_id , create_time = row .create_time ,
369418 finish_time = row .finish_time ,
419+ duration = duration ,
420+ total_tokens = total_tokens ,
370421 question = row .question , sql_answer = row .sql_answer , sql = row .sql ,
371422 chart_answer = row .chart_answer , chart = row .chart ,
372423 analysis = row .analysis , predict = row .predict ,
@@ -437,6 +488,23 @@ def format_record(record: ChatRecordResult):
437488 _dict ['sql' ] = sqlparse .format (record .sql , reindent = True )
438489 except Exception :
439490 pass
491+
492+ # 格式化duration字段,保留2位小数
493+ if 'duration' in _dict and _dict ['duration' ] is not None :
494+ try :
495+ # 可以格式化为更易读的形式
496+ _dict ['duration' ] = round (_dict ['duration' ], 2 ) # 保留2位小数
497+ except Exception :
498+ pass
499+
500+ # 格式化total_tokens字段
501+ if 'total_tokens' in _dict and _dict ['total_tokens' ] is not None :
502+ try :
503+ # 确保是整数类型
504+ _dict ['total_tokens' ] = int (_dict ['total_tokens' ]) if _dict ['total_tokens' ] else 0
505+ except Exception :
506+ _dict ['total_tokens' ] = 0
507+
440508 # 去除返回前端多余的字段
441509 _dict .pop ('sql_reasoning_content' , None )
442510 _dict .pop ('chart_reasoning_content' , None )
@@ -446,6 +514,90 @@ def format_record(record: ChatRecordResult):
446514 return _dict
447515
448516
517+ def get_chat_log_history (session : SessionDep , chat_record_id : int , current_user : CurrentUser ) -> ChatLogHistory :
518+ """
519+ 获取ChatRecord的详细历史记录
520+
521+ Args:
522+ session: 数据库会话
523+ chat_record_id: ChatRecord的ID
524+ current_user: 当前用户
525+
526+ Returns:
527+ ChatLogHistory: 包含历史步骤和时间信息的对象
528+ """
529+ # 1. 首先验证ChatRecord存在且属于当前用户
530+ chat_record = session .get (ChatRecord , chat_record_id )
531+ if not chat_record :
532+ raise Exception (f"ChatRecord with id { chat_record_id } not found" )
533+
534+ if chat_record .create_by != current_user .id :
535+ raise Exception (f"ChatRecord with id { chat_record_id } not owned by the current user" )
536+
537+ # 2. 查询与该ChatRecord相关的所有ChatLog记录
538+ chat_logs = session .query (ChatLog ).filter (
539+ ChatLog .pid == chat_record_id
540+ ).order_by (ChatLog .start_time ).all ()
541+
542+ # 3. 计算总的时间和token信息
543+ total_tokens = 0
544+ steps = []
545+
546+ for log in chat_logs :
547+ # 计算单条记录的耗时
548+ duration = None
549+ if log .start_time and log .finish_time :
550+ try :
551+ time_diff = log .finish_time - log .start_time
552+ duration = time_diff .total_seconds ()
553+ except Exception :
554+ duration = None
555+
556+ # 计算单条记录的token消耗
557+ log_tokens = 0
558+ if log .token_usage is not None :
559+ if isinstance (log .token_usage , dict ):
560+ if log .token_usage and "total_tokens" in log .token_usage :
561+ token_value = log .token_usage ["total_tokens" ]
562+ if isinstance (token_value , (int , float )):
563+ log_tokens = int (token_value )
564+ elif isinstance (log .token_usage , (int , float )):
565+ log_tokens = log .token_usage
566+
567+ # 累加到总token消耗
568+ total_tokens += log_tokens
569+
570+ # 创建ChatLogHistoryItem
571+ history_item = ChatLogHistoryItem (
572+ start_time = log .start_time ,
573+ finish_time = log .finish_time ,
574+ duration = duration ,
575+ total_tokens = log_tokens ,
576+ operate = log .operate ,
577+ local_operation = log .local_operation
578+ )
579+ steps .append (history_item )
580+
581+ # 4. 计算总耗时(使用ChatRecord的时间)
582+ total_duration = None
583+ if chat_record .create_time and chat_record .finish_time :
584+ try :
585+ time_diff = chat_record .finish_time - chat_record .create_time
586+ total_duration = time_diff .total_seconds ()
587+ except Exception :
588+ total_duration = None
589+
590+ # 5. 创建并返回ChatLogHistory对象
591+ chat_log_history = ChatLogHistory (
592+ start_time = chat_record .create_time , # 使用ChatRecord的create_time
593+ finish_time = chat_record .finish_time , # 使用ChatRecord的finish_time
594+ duration = total_duration ,
595+ total_tokens = total_tokens ,
596+ steps = steps
597+ )
598+
599+ return chat_log_history
600+
449601def get_chat_brief_generate (session : SessionDep , chat_id : int ):
450602 chat = get_chat (session = session , chat_id = chat_id )
451603 if chat is not None and chat .brief_generate is not None :
@@ -877,7 +1029,12 @@ def save_error_message(session: SessionDep, record_id: int, message: str) -> Cha
8771029
8781030 session .commit ()
8791031
880- # todo log error finish
1032+ # log error finish
1033+ stmt = update (ChatLog ).where (and_ (ChatLog .pid == record .id , ChatLog .finish_time .is_ (None ))).values (
1034+ finish_time = record .finish_time
1035+ )
1036+ session .execute (stmt )
1037+ session .commit ()
8811038
8821039 return result
8831040
0 commit comments