lite-agent 0.8.0__py3-none-any.whl → 0.10.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.

@@ -454,7 +454,7 @@ def _display_single_message_compact(
454
454
  show_timestamp: bool = False,
455
455
  local_timezone: timezone | None = None,
456
456
  ) -> None:
457
- """以紧凑格式打印单个消息。"""
457
+ """以列式格式打印单个消息,类似 rich log。"""
458
458
 
459
459
  def truncate_content(content: str, max_length: int) -> str:
460
460
  """截断内容并添加省略号。"""
@@ -462,20 +462,265 @@ def _display_single_message_compact(
462
462
  return content
463
463
  return content[: max_length - 3] + "..."
464
464
 
465
- # 创建消息上下文
466
- context_config = {
467
- "console": console,
468
- "index": index,
469
- "message": message,
470
- "max_content_length": max_content_length,
471
- "truncate_content": truncate_content,
472
- "show_timestamp": show_timestamp,
473
- "local_timezone": local_timezone,
474
- }
475
- 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)
476
470
 
477
- # 根据消息类型分发处理
478
- _dispatch_message_display(message, context)
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
+ # 第一行显示 User: 标签
536
+ table.add_row(
537
+ f"[dim]{time_str:8}[/dim]",
538
+ f"[dim]{index_str:4}[/dim]",
539
+ "[blue]User:[/blue]",
540
+ )
541
+ # 如果有内容,添加内容行
542
+ if line:
543
+ table.add_row("", "", line)
544
+ else:
545
+ # 续行只在内容列显示
546
+ table.add_row("", "", line)
547
+
548
+ console.print(table)
549
+
550
+
551
+ def _display_system_message_with_columns(
552
+ message: NewSystemMessage,
553
+ console: Console,
554
+ time_str: str,
555
+ index_str: str,
556
+ max_content_length: int,
557
+ truncate_content: Callable[[str, int], str],
558
+ ) -> None:
559
+ """使用列布局显示系统消息。"""
560
+ content = truncate_content(message.content, max_content_length)
561
+
562
+ # 创建表格来确保对齐
563
+ table = Table.grid(padding=0)
564
+ table.add_column(width=8, justify="left") # 时间列
565
+ table.add_column(width=4, justify="left") # 序号列
566
+ table.add_column(min_width=0) # 内容列
567
+
568
+ lines = content.split("\n")
569
+ for i, line in enumerate(lines):
570
+ if i == 0:
571
+ # 第一行显示完整信息
572
+ table.add_row(
573
+ f"[dim]{time_str:8}[/dim]",
574
+ f"[dim]{index_str:4}[/dim]",
575
+ f"[yellow]System:[/yellow] {line}",
576
+ )
577
+ else:
578
+ # 续行只在内容列显示
579
+ table.add_row("", "", line)
580
+
581
+ console.print(table)
582
+
583
+
584
+ def _display_assistant_message_with_columns(
585
+ message: NewAssistantMessage,
586
+ console: Console,
587
+ time_str: str,
588
+ index_str: str,
589
+ max_content_length: int,
590
+ truncate_content: Callable[[str, int], str],
591
+ ) -> None:
592
+ """使用列布局显示助手消息。"""
593
+ # 提取内容
594
+ text_parts = []
595
+ tool_calls = []
596
+ tool_results = []
597
+
598
+ for item in message.content:
599
+ if item.type == "text":
600
+ text_parts.append(item.text)
601
+ elif item.type == "tool_call":
602
+ tool_calls.append(item)
603
+ elif item.type == "tool_call_result":
604
+ tool_results.append(item)
605
+
606
+ # 构建元信息
607
+ meta_info = ""
608
+ if message.meta:
609
+ meta_parts = []
610
+ if message.meta.model is not None:
611
+ meta_parts.append(f"Model:{message.meta.model}")
612
+ if message.meta.latency_ms is not None:
613
+ meta_parts.append(f"Latency:{message.meta.latency_ms}ms")
614
+ if message.meta.total_time_ms is not None:
615
+ meta_parts.append(f"Output:{message.meta.total_time_ms}ms")
616
+ if message.meta.usage and message.meta.usage.input_tokens is not None and message.meta.usage.output_tokens is not None:
617
+ total_tokens = message.meta.usage.input_tokens + message.meta.usage.output_tokens
618
+ meta_parts.append(f"Tokens:↑{message.meta.usage.input_tokens}↓{message.meta.usage.output_tokens}={total_tokens}")
619
+
620
+ if meta_parts:
621
+ meta_info = f" [dim]({' | '.join(meta_parts)})[/dim]"
622
+
623
+ # 创建表格来确保对齐
624
+ table = Table.grid(padding=0)
625
+ table.add_column(width=8, justify="left") # 时间列
626
+ table.add_column(width=4, justify="left") # 序号列
627
+ table.add_column(min_width=0) # 内容列
628
+
629
+ # 处理文本内容
630
+ first_row_added = False
631
+ if text_parts:
632
+ content = " ".join(text_parts)
633
+ content = truncate_content(content, max_content_length)
634
+ lines = content.split("\n")
635
+ for i, line in enumerate(lines):
636
+ if i == 0:
637
+ # 第一行显示 Assistant: 标签
638
+ table.add_row(
639
+ f"[dim]{time_str:8}[/dim]",
640
+ f"[dim]{index_str:4}[/dim]",
641
+ f"[green]Assistant:[/green]{meta_info}",
642
+ )
643
+ # 如果有内容,添加内容行
644
+ if line:
645
+ table.add_row("", "", line)
646
+ first_row_added = True
647
+ else:
648
+ # 续行只在内容列显示
649
+ table.add_row("", "", line)
650
+
651
+ # 如果没有文本内容,只显示助手消息头
652
+ if not first_row_added:
653
+ table.add_row(
654
+ f"[dim]{time_str:8}[/dim]",
655
+ f"[dim]{index_str:4}[/dim]",
656
+ f"[green]Assistant:[/green]{meta_info}",
657
+ )
658
+
659
+ # 添加工具调用
660
+ for tool_call in tool_calls:
661
+ args_str = ""
662
+ if tool_call.arguments:
663
+ try:
664
+ parsed_args = json.loads(tool_call.arguments) if isinstance(tool_call.arguments, str) else tool_call.arguments
665
+ args_str = f" {parsed_args}"
666
+ except (json.JSONDecodeError, TypeError):
667
+ args_str = f" {tool_call.arguments}"
668
+
669
+ args_display = truncate_content(args_str, max_content_length - len(tool_call.name) - 10)
670
+ table.add_row("", "", f"[magenta]Call:[/magenta] {tool_call.name}{args_display}")
671
+
672
+ # 添加工具结果
673
+ for tool_result in tool_results:
674
+ output = truncate_content(str(tool_result.output), max_content_length)
675
+ time_info = ""
676
+ if tool_result.execution_time_ms is not None:
677
+ time_info = f" [dim]({tool_result.execution_time_ms}ms)[/dim]"
678
+
679
+ table.add_row("", "", f"[cyan]Output:[/cyan]{time_info}")
680
+ lines = output.split("\n")
681
+ for line in lines:
682
+ table.add_row("", "", line)
683
+
684
+ console.print(table)
685
+
686
+
687
+ def _display_legacy_message_with_columns(
688
+ message: FlexibleRunnerMessage,
689
+ console: Console,
690
+ time_str: str,
691
+ index_str: str,
692
+ max_content_length: int,
693
+ truncate_content: Callable[[str, int], str],
694
+ ) -> None:
695
+ """使用列布局显示旧格式消息。"""
696
+ # 这里可以处理旧格式消息,暂时简单显示
697
+ try:
698
+ content = str(message.model_dump()) if hasattr(message, "model_dump") else str(message) # type: ignore[attr-defined]
699
+ except Exception:
700
+ content = str(message)
701
+
702
+ content = truncate_content(content, max_content_length)
703
+
704
+ # 创建表格来确保对齐
705
+ table = Table.grid(padding=0)
706
+ table.add_column(width=8, justify="left") # 时间列
707
+ table.add_column(width=4, justify="left") # 序号列
708
+ table.add_column(min_width=0) # 内容列
709
+
710
+ lines = content.split("\n")
711
+ for i, line in enumerate(lines):
712
+ if i == 0:
713
+ # 第一行显示完整信息
714
+ table.add_row(
715
+ f"[dim]{time_str:8}[/dim]",
716
+ f"[dim]{index_str:4}[/dim]",
717
+ f"[red]Legacy:[/red] {line}",
718
+ )
719
+ else:
720
+ # 续行只在内容列显示
721
+ table.add_row("", "", line)
722
+
723
+ console.print(table)
479
724
 
480
725
 
481
726
  def _create_message_context(context_config: dict[str, FlexibleRunnerMessage | Console | int | bool | timezone | Callable[[str, int], str] | None]) -> MessageContext:
@@ -581,6 +826,8 @@ def _display_assistant_message_compact_v2(message: AgentAssistantMessage, contex
581
826
  meta_info = ""
582
827
  if message.meta:
583
828
  meta_parts = []
829
+ if message.meta.model is not None:
830
+ meta_parts.append(f"Model:{message.meta.model}")
584
831
  if message.meta.latency_ms is not None:
585
832
  meta_parts.append(f"Latency:{message.meta.latency_ms}ms")
586
833
  if message.meta.output_time_ms is not None:
@@ -656,7 +903,11 @@ def _display_dict_function_call_compact(message: dict, context: MessageContext)
656
903
  def _display_dict_function_output_compact(message: dict, context: MessageContext) -> None:
657
904
  """显示字典类型的函数输出消息。"""
658
905
  output = context.truncate_content(str(message.get("output", "")), context.max_content_length)
659
- context.console.print(f"{context.timestamp_str}{context.index_str}[cyan]Output:[/cyan]")
906
+ # Add execution time if available
907
+ time_info = ""
908
+ if message.get("execution_time_ms") is not None:
909
+ time_info = f" [dim]({message['execution_time_ms']}ms)[/dim]"
910
+ context.console.print(f"{context.timestamp_str}{context.index_str}[cyan]Output:[/cyan]{time_info}")
660
911
  context.console.print(f"{output}")
661
912
 
662
913
 
@@ -676,6 +927,8 @@ def _display_dict_assistant_compact(message: dict, context: MessageContext) -> N
676
927
  meta = message.get("meta")
677
928
  if meta and isinstance(meta, dict):
678
929
  meta_parts = []
930
+ if meta.get("model") is not None:
931
+ meta_parts.append(f"Model:{meta['model']}")
679
932
  if meta.get("latency_ms") is not None:
680
933
  meta_parts.append(f"Latency:{meta['latency_ms']}ms")
681
934
  if meta.get("output_time_ms") is not None:
@@ -743,30 +996,34 @@ def _display_new_assistant_message_compact(message: NewAssistantMessage, context
743
996
  elif item.type == "tool_call_result":
744
997
  tool_results.append(item)
745
998
 
746
- # Display text content first if available
747
- if text_parts:
748
- content = " ".join(text_parts)
749
- content = context.truncate_content(content, context.max_content_length)
750
-
751
- # Add meta data information (使用英文标签)
752
- meta_info = ""
753
- if message.meta:
754
- meta_parts = []
755
- if message.meta.latency_ms is not None:
756
- meta_parts.append(f"Latency:{message.meta.latency_ms}ms")
757
- if message.meta.total_time_ms is not None:
758
- meta_parts.append(f"Output:{message.meta.total_time_ms}ms")
759
- if message.meta.usage and message.meta.usage.input_tokens is not None and message.meta.usage.output_tokens is not None:
760
- total_tokens = message.meta.usage.input_tokens + message.meta.usage.output_tokens
761
- meta_parts.append(f"Tokens:↑{message.meta.usage.input_tokens}↓{message.meta.usage.output_tokens}={total_tokens}")
762
-
763
- if meta_parts:
764
- meta_info = f" [dim]({' | '.join(meta_parts)})[/dim]"
999
+ # Add meta data information (使用英文标签)
1000
+ meta_info = ""
1001
+ if message.meta:
1002
+ meta_parts = []
1003
+ if message.meta.model is not None:
1004
+ meta_parts.append(f"Model:{message.meta.model}")
1005
+ if message.meta.latency_ms is not None:
1006
+ meta_parts.append(f"Latency:{message.meta.latency_ms}ms")
1007
+ if message.meta.total_time_ms is not None:
1008
+ meta_parts.append(f"Output:{message.meta.total_time_ms}ms")
1009
+ if message.meta.usage and message.meta.usage.input_tokens is not None and message.meta.usage.output_tokens is not None:
1010
+ total_tokens = message.meta.usage.input_tokens + message.meta.usage.output_tokens
1011
+ meta_parts.append(f"Tokens:↑{message.meta.usage.input_tokens}↓{message.meta.usage.output_tokens}={total_tokens}")
765
1012
 
1013
+ if meta_parts:
1014
+ meta_info = f" [dim]({' | '.join(meta_parts)})[/dim]"
1015
+
1016
+ # Always show Assistant header if there's any content (text, tool calls, or results)
1017
+ if text_parts or tool_calls or tool_results:
766
1018
  context.console.print(f"{context.timestamp_str}{context.index_str}[green]Assistant:[/green]{meta_info}")
767
- context.console.print(f"{content}")
768
1019
 
769
- # Display tool calls
1020
+ # Display text content if available
1021
+ if text_parts:
1022
+ content = " ".join(text_parts)
1023
+ content = context.truncate_content(content, context.max_content_length)
1024
+ context.console.print(f"{content}")
1025
+
1026
+ # Display tool calls with proper indentation
770
1027
  for tool_call in tool_calls:
771
1028
  args_str = ""
772
1029
  if tool_call.arguments:
@@ -777,11 +1034,17 @@ def _display_new_assistant_message_compact(message: NewAssistantMessage, context
777
1034
  args_str = f" {tool_call.arguments}"
778
1035
 
779
1036
  args_display = context.truncate_content(args_str, context.max_content_length - len(tool_call.name) - 10)
780
- context.console.print(f"{context.timestamp_str}{context.index_str}[magenta]Call:[/magenta]")
781
- context.console.print(f"{tool_call.name}{args_display}")
1037
+ # Always use indented format for better hierarchy
1038
+ context.console.print(f" [magenta]Call:[/magenta] {tool_call.name}{args_display}")
782
1039
 
783
- # Display tool results
1040
+ # Display tool results with proper indentation
784
1041
  for tool_result in tool_results:
785
1042
  output = context.truncate_content(str(tool_result.output), context.max_content_length)
786
- context.console.print(f"{context.timestamp_str}{context.index_str}[cyan]Output:[/cyan]")
787
- context.console.print(f"{output}")
1043
+ # Add execution time if available
1044
+ time_info = ""
1045
+ if tool_result.execution_time_ms is not None:
1046
+ time_info = f" [dim]({tool_result.execution_time_ms}ms)[/dim]"
1047
+
1048
+ # Always use indented format for better hierarchy
1049
+ context.console.print(f" [cyan]Output:[/cyan]{time_info}")
1050
+ context.console.print(f" {output}")
lite_agent/client.py CHANGED
@@ -37,7 +37,7 @@ def parse_reasoning_config(reasoning: ReasoningConfig) -> tuple[ReasoningEffort
37
37
  Args:
38
38
  reasoning: 统一的推理配置
39
39
  - str: "minimal", "low", "medium", "high" -> reasoning_effort
40
- - dict: {"type": "enabled", "budget_tokens": N} -> thinking_config
40
+ - dict: {"effort": "minimal"} -> reasoning_effort, 其他格式 -> thinking_config
41
41
  - bool: True -> "medium", False -> None
42
42
  - None: 不启用推理
43
43
 
@@ -46,19 +46,26 @@ def parse_reasoning_config(reasoning: ReasoningConfig) -> tuple[ReasoningEffort
46
46
  """
47
47
  if reasoning is None:
48
48
  return None, None
49
+
49
50
  if isinstance(reasoning, str):
50
51
  # 字符串类型,使用 reasoning_effort
51
52
  # 确保字符串是有效的 ReasoningEffort 值
52
53
  if reasoning in ("minimal", "low", "medium", "high"):
53
54
  return reasoning, None # type: ignore[return-value]
54
- return None, None
55
- if isinstance(reasoning, dict):
56
- # 字典类型,使用 thinking_config
57
- return None, reasoning
58
- if isinstance(reasoning, bool):
55
+ elif isinstance(reasoning, dict):
56
+ # 检查是否为 {"effort": "value"} 格式
57
+ if "effort" in reasoning and len(reasoning) == 1:
58
+ effort = reasoning["effort"]
59
+ if isinstance(effort, str) and effort in ("minimal", "low", "medium", "high"):
60
+ return effort, None # type: ignore[return-value]
61
+ else:
62
+ # 其他字典格式,作为 thinking_config
63
+ return None, reasoning
64
+ elif isinstance(reasoning, bool):
59
65
  # 布尔类型,True 使用默认的 medium,False 不启用
60
- return "medium" if reasoning else None, None
61
- # 其他类型,默认不启用
66
+ return ("medium", None) if reasoning else (None, None)
67
+
68
+ # 其他类型或无效格式,默认不启用
62
69
  return None, None
63
70
 
64
71
 
@@ -5,6 +5,8 @@ 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
+ import json
9
+
8
10
  from lite_agent.types import NewUserMessage, RunnerMessages, UserTextContent
9
11
 
10
12
 
@@ -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>")
@@ -119,8 +119,10 @@ class ResponseEventProcessor:
119
119
  # Extract model information from event
120
120
  model_name = getattr(event, "model", None)
121
121
  # Debug: check if event has model info in different location
122
- if hasattr(event, "response") and hasattr(event.response, "model"):
123
- model_name = getattr(event.response, "model", None)
122
+ if hasattr(event, "response"):
123
+ response = getattr(event, "response", None)
124
+ if response and hasattr(response, "model"):
125
+ model_name = getattr(response, "model", None)
124
126
  # Create usage information
125
127
  usage = MessageUsage(
126
128
  input_tokens=self._usage_data.get("input_tokens"),