lite-agent 0.6.0__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lite-agent might be problematic. Click here for more details.

@@ -26,6 +26,8 @@ from lite_agent.types import (
26
26
  AgentSystemMessage,
27
27
  AgentUserMessage,
28
28
  AssistantMessageMeta,
29
+ AssistantToolCall,
30
+ AssistantToolCallResult,
29
31
  BasicMessageMeta,
30
32
  FlexibleRunnerMessage,
31
33
  LLMResponseMeta,
@@ -228,9 +230,9 @@ def _update_message_counts(message: FlexibleRunnerMessage, counts: dict[str, int
228
230
  counts["Assistant"] += 1
229
231
  # Count tool calls and outputs within the assistant message
230
232
  for content_item in message.content:
231
- if content_item.type == "tool_call":
233
+ if isinstance(content_item, AssistantToolCall):
232
234
  counts["Function Call"] += 1
233
- elif content_item.type == "tool_call_result":
235
+ elif isinstance(content_item, AssistantToolCallResult):
234
236
  counts["Function Output"] += 1
235
237
  elif isinstance(message, NewSystemMessage):
236
238
  counts["System"] += 1
@@ -295,10 +297,18 @@ def _process_object_meta(meta: BasicMessageMeta | LLMResponseMeta | AssistantMes
295
297
  """处理对象类型的 meta 数据。"""
296
298
  # LLMResponseMeta 和 AssistantMessageMeta 都有这些字段
297
299
  if isinstance(meta, (LLMResponseMeta, AssistantMessageMeta)):
298
- if hasattr(meta, "input_tokens") and meta.input_tokens is not None:
299
- total_input += int(meta.input_tokens)
300
- if hasattr(meta, "output_tokens") and meta.output_tokens is not None:
301
- total_output += int(meta.output_tokens)
300
+ # For AssistantMessageMeta, use the structured usage field
301
+ if isinstance(meta, AssistantMessageMeta) and meta.usage is not None:
302
+ if meta.usage.input_tokens is not None:
303
+ total_input += int(meta.usage.input_tokens)
304
+ if meta.usage.output_tokens is not None:
305
+ total_output += int(meta.usage.output_tokens)
306
+ # For LLMResponseMeta, use the flat fields
307
+ elif isinstance(meta, LLMResponseMeta):
308
+ if hasattr(meta, "input_tokens") and meta.input_tokens is not None:
309
+ total_input += int(meta.input_tokens)
310
+ if hasattr(meta, "output_tokens") and meta.output_tokens is not None:
311
+ total_output += int(meta.output_tokens)
302
312
  if hasattr(meta, "latency_ms") and meta.latency_ms is not None:
303
313
  total_latency += int(meta.latency_ms)
304
314
  if hasattr(meta, "output_time_ms") and meta.output_time_ms is not None:
@@ -363,11 +373,9 @@ def display_chat_summary(messages: RunnerMessages, *, console: Console | None =
363
373
  messages: 要汇总的消息列表
364
374
  console: Rich Console 实例,如果为 None 则创建新的
365
375
  """
366
- if console is None:
367
- console = Console()
368
-
376
+ active_console = console or Console()
369
377
  summary_table = build_chat_summary_table(messages)
370
- console.print(summary_table)
378
+ active_console.print(summary_table)
371
379
 
372
380
 
373
381
  def display_messages(
@@ -446,7 +454,7 @@ def _display_single_message_compact(
446
454
  show_timestamp: bool = False,
447
455
  local_timezone: timezone | None = None,
448
456
  ) -> None:
449
- """以紧凑格式打印单个消息。"""
457
+ """以列式格式打印单个消息,类似 rich log。"""
450
458
 
451
459
  def truncate_content(content: str, max_length: int) -> str:
452
460
  """截断内容并添加省略号。"""
@@ -454,20 +462,259 @@ def _display_single_message_compact(
454
462
  return content
455
463
  return content[: max_length - 3] + "..."
456
464
 
457
- # 创建消息上下文
458
- context_config = {
459
- "console": console,
460
- "index": index,
461
- "message": message,
462
- "max_content_length": max_content_length,
463
- "truncate_content": truncate_content,
464
- "show_timestamp": show_timestamp,
465
- "local_timezone": local_timezone,
466
- }
467
- context = _create_message_context(context_config)
465
+ # 获取时间戳
466
+ timestamp = None
467
+ if show_timestamp:
468
+ message_time = _extract_message_time(message)
469
+ timestamp = _format_timestamp(message_time, local_timezone=local_timezone)
470
+
471
+ # 创建列式显示
472
+ _display_message_in_columns(message, console, index, timestamp, max_content_length, truncate_content)
473
+
474
+
475
+ def _display_message_in_columns(
476
+ message: FlexibleRunnerMessage,
477
+ console: Console,
478
+ index: int | None,
479
+ timestamp: str | None,
480
+ max_content_length: int,
481
+ truncate_content: Callable[[str, int], str],
482
+ ) -> None:
483
+ """以列式格式显示消息,类似 rich log。"""
484
+
485
+ # 构建时间和索引列
486
+ time_str = timestamp or ""
487
+ index_str = f"#{index:2d}" if index is not None else ""
488
+
489
+ # 根据消息类型处理内容
490
+ if isinstance(message, NewUserMessage):
491
+ _display_user_message_with_columns(message, console, time_str, index_str, max_content_length, truncate_content)
492
+ elif isinstance(message, NewAssistantMessage):
493
+ _display_assistant_message_with_columns(message, console, time_str, index_str, max_content_length, truncate_content)
494
+ elif isinstance(message, NewSystemMessage):
495
+ _display_system_message_with_columns(message, console, time_str, index_str, max_content_length, truncate_content)
496
+ else:
497
+ # 处理旧格式消息
498
+ _display_legacy_message_with_columns(message, console, time_str, index_str, max_content_length, truncate_content)
499
+
500
+
501
+ def _display_user_message_with_columns(
502
+ message: NewUserMessage,
503
+ console: Console,
504
+ time_str: str,
505
+ index_str: str,
506
+ max_content_length: int,
507
+ truncate_content: Callable[[str, int], str],
508
+ ) -> None:
509
+ """使用列布局显示用户消息。"""
510
+ content_parts = []
511
+ for item in message.content:
512
+ if item.type == "text":
513
+ content_parts.append(item.text)
514
+ elif item.type == "image":
515
+ if item.image_url:
516
+ content_parts.append(f"[Image: {item.image_url}]")
517
+ elif item.file_id:
518
+ content_parts.append(f"[Image: {item.file_id}]")
519
+ elif item.type == "file":
520
+ file_name = item.file_name or item.file_id
521
+ content_parts.append(f"[File: {file_name}]")
522
+
523
+ content = " ".join(content_parts)
524
+ content = truncate_content(content, max_content_length)
525
+
526
+ # 创建表格来确保对齐
527
+ table = Table.grid(padding=0)
528
+ table.add_column(width=8, justify="left") # 时间列
529
+ table.add_column(width=4, justify="left") # 序号列
530
+ table.add_column(min_width=0) # 内容列
531
+
532
+ lines = content.split("\n")
533
+ for i, line in enumerate(lines):
534
+ if i == 0:
535
+ # 第一行显示完整信息
536
+ table.add_row(
537
+ f"[dim]{time_str:8}[/dim]",
538
+ f"[dim]{index_str:4}[/dim]",
539
+ f"[blue]User:[/blue] {line}",
540
+ )
541
+ else:
542
+ # 续行只在内容列显示
543
+ table.add_row("", "", line)
544
+
545
+ console.print(table)
546
+
547
+
548
+ def _display_system_message_with_columns(
549
+ message: NewSystemMessage,
550
+ console: Console,
551
+ time_str: str,
552
+ index_str: str,
553
+ max_content_length: int,
554
+ truncate_content: Callable[[str, int], str],
555
+ ) -> None:
556
+ """使用列布局显示系统消息。"""
557
+ content = truncate_content(message.content, max_content_length)
558
+
559
+ # 创建表格来确保对齐
560
+ table = Table.grid(padding=0)
561
+ table.add_column(width=8, justify="left") # 时间列
562
+ table.add_column(width=4, justify="left") # 序号列
563
+ table.add_column(min_width=0) # 内容列
564
+
565
+ lines = content.split("\n")
566
+ for i, line in enumerate(lines):
567
+ if i == 0:
568
+ # 第一行显示完整信息
569
+ table.add_row(
570
+ f"[dim]{time_str:8}[/dim]",
571
+ f"[dim]{index_str:4}[/dim]",
572
+ f"[yellow]System:[/yellow] {line}",
573
+ )
574
+ else:
575
+ # 续行只在内容列显示
576
+ table.add_row("", "", line)
577
+
578
+ console.print(table)
579
+
580
+
581
+ def _display_assistant_message_with_columns(
582
+ message: NewAssistantMessage,
583
+ console: Console,
584
+ time_str: str,
585
+ index_str: str,
586
+ max_content_length: int,
587
+ truncate_content: Callable[[str, int], str],
588
+ ) -> None:
589
+ """使用列布局显示助手消息。"""
590
+ # 提取内容
591
+ text_parts = []
592
+ tool_calls = []
593
+ tool_results = []
594
+
595
+ for item in message.content:
596
+ if item.type == "text":
597
+ text_parts.append(item.text)
598
+ elif item.type == "tool_call":
599
+ tool_calls.append(item)
600
+ elif item.type == "tool_call_result":
601
+ tool_results.append(item)
602
+
603
+ # 构建元信息
604
+ meta_info = ""
605
+ if message.meta:
606
+ meta_parts = []
607
+ if message.meta.model is not None:
608
+ meta_parts.append(f"Model:{message.meta.model}")
609
+ if message.meta.latency_ms is not None:
610
+ meta_parts.append(f"Latency:{message.meta.latency_ms}ms")
611
+ if message.meta.total_time_ms is not None:
612
+ meta_parts.append(f"Output:{message.meta.total_time_ms}ms")
613
+ if message.meta.usage and message.meta.usage.input_tokens is not None and message.meta.usage.output_tokens is not None:
614
+ total_tokens = message.meta.usage.input_tokens + message.meta.usage.output_tokens
615
+ meta_parts.append(f"Tokens:↑{message.meta.usage.input_tokens}↓{message.meta.usage.output_tokens}={total_tokens}")
616
+
617
+ if meta_parts:
618
+ meta_info = f" [dim]({' | '.join(meta_parts)})[/dim]"
619
+
620
+ # 创建表格来确保对齐
621
+ table = Table.grid(padding=0)
622
+ table.add_column(width=8, justify="left") # 时间列
623
+ table.add_column(width=4, justify="left") # 序号列
624
+ table.add_column(min_width=0) # 内容列
625
+
626
+ # 处理文本内容
627
+ first_row_added = False
628
+ if text_parts:
629
+ content = " ".join(text_parts)
630
+ content = truncate_content(content, max_content_length)
631
+ lines = content.split("\n")
632
+ for i, line in enumerate(lines):
633
+ if i == 0:
634
+ # 第一行显示完整信息
635
+ table.add_row(
636
+ f"[dim]{time_str:8}[/dim]",
637
+ f"[dim]{index_str:4}[/dim]",
638
+ f"[green]Assistant:[/green]{meta_info} {line}",
639
+ )
640
+ first_row_added = True
641
+ else:
642
+ # 续行只在内容列显示
643
+ table.add_row("", "", line)
644
+
645
+ # 如果没有文本内容,只显示助手消息头
646
+ if not first_row_added:
647
+ table.add_row(
648
+ f"[dim]{time_str:8}[/dim]",
649
+ f"[dim]{index_str:4}[/dim]",
650
+ f"[green]Assistant:[/green]{meta_info}",
651
+ )
652
+
653
+ # 添加工具调用
654
+ for tool_call in tool_calls:
655
+ args_str = ""
656
+ if tool_call.arguments:
657
+ try:
658
+ parsed_args = json.loads(tool_call.arguments) if isinstance(tool_call.arguments, str) else tool_call.arguments
659
+ args_str = f" {parsed_args}"
660
+ except (json.JSONDecodeError, TypeError):
661
+ args_str = f" {tool_call.arguments}"
662
+
663
+ args_display = truncate_content(args_str, max_content_length - len(tool_call.name) - 10)
664
+ table.add_row("", "", f"[magenta]Call:[/magenta] {tool_call.name}{args_display}")
665
+
666
+ # 添加工具结果
667
+ for tool_result in tool_results:
668
+ output = truncate_content(str(tool_result.output), max_content_length)
669
+ time_info = ""
670
+ if tool_result.execution_time_ms is not None:
671
+ time_info = f" [dim]({tool_result.execution_time_ms}ms)[/dim]"
672
+
673
+ table.add_row("", "", f"[cyan]Output:[/cyan]{time_info}")
674
+ lines = output.split("\n")
675
+ for line in lines:
676
+ table.add_row("", "", line)
677
+
678
+ console.print(table)
679
+
680
+
681
+ def _display_legacy_message_with_columns(
682
+ message: FlexibleRunnerMessage,
683
+ console: Console,
684
+ time_str: str,
685
+ index_str: str,
686
+ max_content_length: int,
687
+ truncate_content: Callable[[str, int], str],
688
+ ) -> None:
689
+ """使用列布局显示旧格式消息。"""
690
+ # 这里可以处理旧格式消息,暂时简单显示
691
+ try:
692
+ content = str(message.model_dump()) if hasattr(message, "model_dump") else str(message) # type: ignore[attr-defined]
693
+ except Exception:
694
+ content = str(message)
695
+
696
+ content = truncate_content(content, max_content_length)
697
+
698
+ # 创建表格来确保对齐
699
+ table = Table.grid(padding=0)
700
+ table.add_column(width=8, justify="left") # 时间列
701
+ table.add_column(width=4, justify="left") # 序号列
702
+ table.add_column(min_width=0) # 内容列
703
+
704
+ lines = content.split("\n")
705
+ for i, line in enumerate(lines):
706
+ if i == 0:
707
+ # 第一行显示完整信息
708
+ table.add_row(
709
+ f"[dim]{time_str:8}[/dim]",
710
+ f"[dim]{index_str:4}[/dim]",
711
+ f"[red]Legacy:[/red] {line}",
712
+ )
713
+ else:
714
+ # 续行只在内容列显示
715
+ table.add_row("", "", line)
468
716
 
469
- # 根据消息类型分发处理
470
- _dispatch_message_display(message, context)
717
+ console.print(table)
471
718
 
472
719
 
473
720
  def _create_message_context(context_config: dict[str, FlexibleRunnerMessage | Console | int | bool | timezone | Callable[[str, int], str] | None]) -> MessageContext:
@@ -573,13 +820,15 @@ def _display_assistant_message_compact_v2(message: AgentAssistantMessage, contex
573
820
  meta_info = ""
574
821
  if message.meta:
575
822
  meta_parts = []
823
+ if message.meta.model is not None:
824
+ meta_parts.append(f"Model:{message.meta.model}")
576
825
  if message.meta.latency_ms is not None:
577
826
  meta_parts.append(f"Latency:{message.meta.latency_ms}ms")
578
827
  if message.meta.output_time_ms is not None:
579
828
  meta_parts.append(f"Output:{message.meta.output_time_ms}ms")
580
- if message.meta.input_tokens is not None and message.meta.output_tokens is not None:
581
- total_tokens = message.meta.input_tokens + message.meta.output_tokens
582
- meta_parts.append(f"Tokens:↑{message.meta.input_tokens}↓{message.meta.output_tokens}={total_tokens}")
829
+ if message.meta.usage and message.meta.usage.input_tokens is not None and message.meta.usage.output_tokens is not None:
830
+ total_tokens = message.meta.usage.input_tokens + message.meta.usage.output_tokens
831
+ meta_parts.append(f"Tokens:↑{message.meta.usage.input_tokens}↓{message.meta.usage.output_tokens}={total_tokens}")
583
832
 
584
833
  if meta_parts:
585
834
  meta_info = f" [dim]({' | '.join(meta_parts)})[/dim]"
@@ -648,7 +897,11 @@ def _display_dict_function_call_compact(message: dict, context: MessageContext)
648
897
  def _display_dict_function_output_compact(message: dict, context: MessageContext) -> None:
649
898
  """显示字典类型的函数输出消息。"""
650
899
  output = context.truncate_content(str(message.get("output", "")), context.max_content_length)
651
- context.console.print(f"{context.timestamp_str}{context.index_str}[cyan]Output:[/cyan]")
900
+ # Add execution time if available
901
+ time_info = ""
902
+ if message.get("execution_time_ms") is not None:
903
+ time_info = f" [dim]({message['execution_time_ms']}ms)[/dim]"
904
+ context.console.print(f"{context.timestamp_str}{context.index_str}[cyan]Output:[/cyan]{time_info}")
652
905
  context.console.print(f"{output}")
653
906
 
654
907
 
@@ -668,6 +921,8 @@ def _display_dict_assistant_compact(message: dict, context: MessageContext) -> N
668
921
  meta = message.get("meta")
669
922
  if meta and isinstance(meta, dict):
670
923
  meta_parts = []
924
+ if meta.get("model") is not None:
925
+ meta_parts.append(f"Model:{meta['model']}")
671
926
  if meta.get("latency_ms") is not None:
672
927
  meta_parts.append(f"Latency:{meta['latency_ms']}ms")
673
928
  if meta.get("output_time_ms") is not None:
@@ -735,30 +990,34 @@ def _display_new_assistant_message_compact(message: NewAssistantMessage, context
735
990
  elif item.type == "tool_call_result":
736
991
  tool_results.append(item)
737
992
 
738
- # Display text content first if available
739
- if text_parts:
740
- content = " ".join(text_parts)
741
- content = context.truncate_content(content, context.max_content_length)
742
-
743
- # Add meta data information (使用英文标签)
744
- meta_info = ""
745
- if message.meta:
746
- meta_parts = []
747
- if message.meta.latency_ms is not None:
748
- meta_parts.append(f"Latency:{message.meta.latency_ms}ms")
749
- if message.meta.total_time_ms is not None:
750
- meta_parts.append(f"Output:{message.meta.total_time_ms}ms")
751
- if message.meta.usage and message.meta.usage.input_tokens is not None and message.meta.usage.output_tokens is not None:
752
- total_tokens = message.meta.usage.input_tokens + message.meta.usage.output_tokens
753
- meta_parts.append(f"Tokens:↑{message.meta.usage.input_tokens}↓{message.meta.usage.output_tokens}={total_tokens}")
754
-
755
- if meta_parts:
756
- meta_info = f" [dim]({' | '.join(meta_parts)})[/dim]"
993
+ # Add meta data information (使用英文标签)
994
+ meta_info = ""
995
+ if message.meta:
996
+ meta_parts = []
997
+ if message.meta.model is not None:
998
+ meta_parts.append(f"Model:{message.meta.model}")
999
+ if message.meta.latency_ms is not None:
1000
+ meta_parts.append(f"Latency:{message.meta.latency_ms}ms")
1001
+ if message.meta.total_time_ms is not None:
1002
+ meta_parts.append(f"Output:{message.meta.total_time_ms}ms")
1003
+ if message.meta.usage and message.meta.usage.input_tokens is not None and message.meta.usage.output_tokens is not None:
1004
+ total_tokens = message.meta.usage.input_tokens + message.meta.usage.output_tokens
1005
+ meta_parts.append(f"Tokens:↑{message.meta.usage.input_tokens}↓{message.meta.usage.output_tokens}={total_tokens}")
757
1006
 
1007
+ if meta_parts:
1008
+ meta_info = f" [dim]({' | '.join(meta_parts)})[/dim]"
1009
+
1010
+ # Always show Assistant header if there's any content (text, tool calls, or results)
1011
+ if text_parts or tool_calls or tool_results:
758
1012
  context.console.print(f"{context.timestamp_str}{context.index_str}[green]Assistant:[/green]{meta_info}")
759
- context.console.print(f"{content}")
760
1013
 
761
- # Display tool calls
1014
+ # Display text content if available
1015
+ if text_parts:
1016
+ content = " ".join(text_parts)
1017
+ content = context.truncate_content(content, context.max_content_length)
1018
+ context.console.print(f"{content}")
1019
+
1020
+ # Display tool calls with proper indentation
762
1021
  for tool_call in tool_calls:
763
1022
  args_str = ""
764
1023
  if tool_call.arguments:
@@ -769,11 +1028,17 @@ def _display_new_assistant_message_compact(message: NewAssistantMessage, context
769
1028
  args_str = f" {tool_call.arguments}"
770
1029
 
771
1030
  args_display = context.truncate_content(args_str, context.max_content_length - len(tool_call.name) - 10)
772
- context.console.print(f"{context.timestamp_str}{context.index_str}[magenta]Call:[/magenta]")
773
- context.console.print(f"{tool_call.name}{args_display}")
1031
+ # Always use indented format for better hierarchy
1032
+ context.console.print(f" [magenta]Call:[/magenta] {tool_call.name}{args_display}")
774
1033
 
775
- # Display tool results
1034
+ # Display tool results with proper indentation
776
1035
  for tool_result in tool_results:
777
1036
  output = context.truncate_content(str(tool_result.output), context.max_content_length)
778
- context.console.print(f"{context.timestamp_str}{context.index_str}[cyan]Output:[/cyan]")
779
- context.console.print(f"{output}")
1037
+ # Add execution time if available
1038
+ time_info = ""
1039
+ if tool_result.execution_time_ms is not None:
1040
+ time_info = f" [dim]({tool_result.execution_time_ms}ms)[/dim]"
1041
+
1042
+ # Always use indented format for better hierarchy
1043
+ context.console.print(f" [cyan]Output:[/cyan]{time_info}")
1044
+ context.console.print(f" {output}")
lite_agent/client.py CHANGED
@@ -100,6 +100,7 @@ class BaseLLMClient(abc.ABC):
100
100
  tools: list[ChatCompletionToolParam] | None = None,
101
101
  tool_choice: str = "auto",
102
102
  reasoning: ReasoningConfig = None,
103
+ *,
103
104
  streaming: bool = True,
104
105
  **kwargs: Any, # noqa: ANN401
105
106
  ) -> Any: # noqa: ANN401
@@ -112,6 +113,7 @@ class BaseLLMClient(abc.ABC):
112
113
  tools: list[FunctionToolParam] | None = None,
113
114
  tool_choice: Literal["none", "auto", "required"] = "auto",
114
115
  reasoning: ReasoningConfig = None,
116
+ *,
115
117
  streaming: bool = True,
116
118
  **kwargs: Any, # noqa: ANN401
117
119
  ) -> Any: # noqa: ANN401
@@ -136,6 +138,7 @@ class LiteLLMClient(BaseLLMClient):
136
138
  tools: list[ChatCompletionToolParam] | None = None,
137
139
  tool_choice: str = "auto",
138
140
  reasoning: ReasoningConfig = None,
141
+ *,
139
142
  streaming: bool = True,
140
143
  **kwargs: Any, # noqa: ANN401
141
144
  ) -> Any: # noqa: ANN401
@@ -187,6 +190,7 @@ class LiteLLMClient(BaseLLMClient):
187
190
  tools: list[FunctionToolParam] | None = None,
188
191
  tool_choice: Literal["none", "auto", "required"] = "auto",
189
192
  reasoning: ReasoningConfig = None,
193
+ *,
190
194
  streaming: bool = True,
191
195
  **kwargs: Any, # noqa: ANN401
192
196
  ) -> Any: # type: ignore[return] # noqa: ANN401
@@ -0,0 +1,30 @@
1
+ from typing import Literal
2
+
3
+
4
+ class CompletionMode:
5
+ """Agent completion modes."""
6
+
7
+ STOP: Literal["stop"] = "stop" # Traditional completion until model decides to stop
8
+ CALL: Literal["call"] = "call" # Completion until specific tool is called
9
+
10
+
11
+ class ToolName:
12
+ """System tool names."""
13
+
14
+ TRANSFER_TO_AGENT = "transfer_to_agent"
15
+ TRANSFER_TO_PARENT = "transfer_to_parent"
16
+ WAIT_FOR_USER = "wait_for_user"
17
+
18
+
19
+ class StreamIncludes:
20
+ """Default stream includes configuration."""
21
+
22
+ DEFAULT_INCLUDES = (
23
+ "completion_raw",
24
+ "usage",
25
+ "function_call",
26
+ "function_call_output",
27
+ "content_delta",
28
+ "function_call_delta",
29
+ "assistant_message",
30
+ )
@@ -5,7 +5,9 @@ This module provides common message transfer functions that can be used
5
5
  with agents to preprocess messages before sending them to the API.
6
6
  """
7
7
 
8
- from lite_agent.types import RunnerMessages
8
+ import json
9
+
10
+ from lite_agent.types import NewUserMessage, RunnerMessages, UserTextContent
9
11
 
10
12
 
11
13
  def consolidate_history_transfer(messages: RunnerMessages) -> RunnerMessages:
@@ -43,8 +45,8 @@ def consolidate_history_transfer(messages: RunnerMessages) -> RunnerMessages:
43
45
  # Create the consolidated message
44
46
  consolidated_content = "以下是目前发生的所有交互:\n\n" + "\n".join(xml_content) + "\n\n接下来该做什么?"
45
47
 
46
- # Return a single user message
47
- return [{"role": "user", "content": consolidated_content}]
48
+ # Return a single user message using NewMessage format
49
+ return [NewUserMessage(content=[UserTextContent(text=consolidated_content)])]
48
50
 
49
51
 
50
52
  def _process_message_to_xml(message: dict | object) -> list[str]:
@@ -67,8 +69,25 @@ def _process_message_to_xml(message: dict | object) -> list[str]:
67
69
 
68
70
  # Handle new message format where content is a list
69
71
  if isinstance(content, list):
70
- # Extract text from content items
71
- text_parts = [item.text for item in content if (hasattr(item, "type") and item.type == "text") or hasattr(item, "text")]
72
+ # Process each content item
73
+ text_parts = []
74
+ for item in content:
75
+ if hasattr(item, "type"):
76
+ if item.type == "text":
77
+ text_parts.append(item.text)
78
+ elif item.type == "tool_call":
79
+ # Handle tool call content
80
+ arguments = item.arguments
81
+ if isinstance(arguments, dict):
82
+ arguments = json.dumps(arguments, ensure_ascii=False)
83
+ xml_lines.append(f" <function_call name='{item.name}' arguments='{arguments}' />")
84
+ elif item.type == "tool_call_result":
85
+ # Handle tool call result content
86
+ xml_lines.append(f" <function_result call_id='{item.call_id}'>{item.output}</function_result>")
87
+ elif hasattr(item, "text"):
88
+ text_parts.append(item.text)
89
+
90
+ # Add text content as message if any
72
91
  content_text = " ".join(text_parts)
73
92
  if content_text:
74
93
  xml_lines.append(f" <message role='{role}'>{content_text}</message>")
@@ -26,6 +26,7 @@ from lite_agent.types import (
26
26
  ToolCallFunction,
27
27
  UsageEvent,
28
28
  )
29
+ from lite_agent.utils.metrics import TimingMetrics
29
30
 
30
31
 
31
32
  class CompletionEventProcessor:
@@ -71,21 +72,18 @@ class CompletionEventProcessor:
71
72
  if not self.yielded_content:
72
73
  self.yielded_content = True
73
74
  end_time = datetime.now(timezone.utc)
74
- latency_ms = None
75
- output_time_ms = None
76
- # latency_ms: 从开始准备输出到 LLM 输出第一个字符的时间差
77
- if self._start_time and self._first_output_time:
78
- latency_ms = int((self._first_output_time - self._start_time).total_seconds() * 1000)
79
- # output_time_ms: 从输出第一个字符到输出完成的时间差
80
- if self._first_output_time and self._output_complete_time:
81
- output_time_ms = int((self._output_complete_time - self._first_output_time).total_seconds() * 1000)
75
+ latency_ms = TimingMetrics.calculate_latency_ms(self._start_time, self._first_output_time)
76
+ output_time_ms = TimingMetrics.calculate_output_time_ms(self._first_output_time, self._output_complete_time)
82
77
 
83
78
  usage = MessageUsage(
84
79
  input_tokens=self._usage_data.get("input_tokens"),
85
80
  output_tokens=self._usage_data.get("output_tokens"),
86
81
  )
82
+ # Extract model information from chunk
83
+ model_name = getattr(chunk, "model", None)
87
84
  meta = AssistantMessageMeta(
88
85
  sent_at=end_time,
86
+ model=model_name,
89
87
  latency_ms=latency_ms,
90
88
  total_time_ms=output_time_ms,
91
89
  usage=usage,
@@ -152,21 +150,18 @@ class CompletionEventProcessor:
152
150
  if not self.yielded_content:
153
151
  self.yielded_content = True
154
152
  end_time = datetime.now(timezone.utc)
155
- latency_ms = None
156
- output_time_ms = None
157
- # latency_ms: 从开始准备输出到 LLM 输出第一个字符的时间差
158
- if self._start_time and self._first_output_time:
159
- latency_ms = int((self._first_output_time - self._start_time).total_seconds() * 1000)
160
- # output_time_ms: 从输出第一个字符到输出完成的时间差
161
- if self._first_output_time and self._output_complete_time:
162
- output_time_ms = int((self._output_complete_time - self._first_output_time).total_seconds() * 1000)
153
+ latency_ms = TimingMetrics.calculate_latency_ms(self._start_time, self._first_output_time)
154
+ output_time_ms = TimingMetrics.calculate_output_time_ms(self._first_output_time, self._output_complete_time)
163
155
 
164
156
  usage = MessageUsage(
165
157
  input_tokens=self._usage_data.get("input_tokens"),
166
158
  output_tokens=self._usage_data.get("output_tokens"),
167
159
  )
160
+ # Extract model information from chunk
161
+ model_name = getattr(chunk, "model", None)
168
162
  meta = AssistantMessageMeta(
169
163
  sent_at=end_time,
164
+ model=model_name,
170
165
  latency_ms=latency_ms,
171
166
  total_time_ms=output_time_ms,
172
167
  usage=usage,
@@ -199,10 +194,9 @@ class CompletionEventProcessor:
199
194
  results.append(UsageEvent(usage=EventUsage(input_tokens=usage["prompt_tokens"], output_tokens=usage["completion_tokens"])))
200
195
 
201
196
  # Then yield timing event if we have timing data
202
- if self._start_time and self._first_output_time and self._output_complete_time:
203
- latency_ms = int((self._first_output_time - self._start_time).total_seconds() * 1000)
204
- output_time_ms = int((self._output_complete_time - self._first_output_time).total_seconds() * 1000)
205
-
197
+ latency_ms = TimingMetrics.calculate_latency_ms(self._start_time, self._first_output_time)
198
+ output_time_ms = TimingMetrics.calculate_output_time_ms(self._first_output_time, self._output_complete_time)
199
+ if latency_ms is not None and output_time_ms is not None:
206
200
  results.append(
207
201
  TimingEvent(
208
202
  timing=Timing(