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.
- lite_agent/agent.py +233 -47
- lite_agent/chat_display.py +319 -54
- lite_agent/client.py +4 -0
- lite_agent/constants.py +30 -0
- lite_agent/message_transfers.py +24 -5
- lite_agent/processors/completion_event_processor.py +14 -20
- lite_agent/processors/response_event_processor.py +23 -15
- lite_agent/response_handlers/__init__.py +1 -0
- lite_agent/response_handlers/base.py +17 -9
- lite_agent/response_handlers/completion.py +35 -7
- lite_agent/response_handlers/responses.py +46 -12
- lite_agent/runner.py +336 -249
- lite_agent/types/__init__.py +2 -0
- lite_agent/types/messages.py +6 -5
- lite_agent/utils/__init__.py +0 -0
- lite_agent/utils/message_builder.py +213 -0
- lite_agent/utils/metrics.py +50 -0
- {lite_agent-0.6.0.dist-info → lite_agent-0.9.0.dist-info}/METADATA +3 -2
- lite_agent-0.9.0.dist-info/RECORD +31 -0
- lite_agent-0.6.0.dist-info/RECORD +0 -27
- {lite_agent-0.6.0.dist-info → lite_agent-0.9.0.dist-info}/WHEEL +0 -0
lite_agent/chat_display.py
CHANGED
|
@@ -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
|
|
233
|
+
if isinstance(content_item, AssistantToolCall):
|
|
232
234
|
counts["Function Call"] += 1
|
|
233
|
-
elif content_item
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
367
|
-
console = Console()
|
|
368
|
-
|
|
376
|
+
active_console = console or Console()
|
|
369
377
|
summary_table = build_chat_summary_table(messages)
|
|
370
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
779
|
-
|
|
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
|
lite_agent/constants.py
ADDED
|
@@ -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
|
+
)
|
lite_agent/message_transfers.py
CHANGED
|
@@ -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
|
-
|
|
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 [
|
|
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
|
-
#
|
|
71
|
-
text_parts = [
|
|
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 =
|
|
75
|
-
output_time_ms =
|
|
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 =
|
|
156
|
-
output_time_ms =
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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(
|