|
@@ -17,7 +17,9 @@ import os
|
|
|
import sys
|
|
import sys
|
|
|
import select
|
|
import select
|
|
|
import asyncio
|
|
import asyncio
|
|
|
|
|
+import json
|
|
|
from pathlib import Path
|
|
from pathlib import Path
|
|
|
|
|
+from typing import Any
|
|
|
|
|
|
|
|
# Clash Verge TUN 模式兼容:禁止 httpx/urllib 自动检测系统 HTTP 代理
|
|
# Clash Verge TUN 模式兼容:禁止 httpx/urllib 自动检测系统 HTTP 代理
|
|
|
# TUN 虚拟网卡已在网络层接管所有流量,不需要应用层再走 HTTP 代理,
|
|
# TUN 虚拟网卡已在网络层接管所有流量,不需要应用层再走 HTTP 代理,
|
|
@@ -80,6 +82,141 @@ def check_stdin() -> str | None:
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+# ===== 格式化打印 =====
|
|
|
|
|
+
|
|
|
|
|
+def _format_json(obj: Any, indent: int = 2) -> str:
|
|
|
|
|
+ """格式化 JSON 对象为字符串"""
|
|
|
|
|
+ try:
|
|
|
|
|
+ return json.dumps(obj, indent=indent, ensure_ascii=False)
|
|
|
|
|
+ except (TypeError, ValueError):
|
|
|
|
|
+ # 如果无法序列化为 JSON,返回字符串表示
|
|
|
|
|
+ return str(obj)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _print_message_details(message: Message):
|
|
|
|
|
+ """完整打印消息的详细信息"""
|
|
|
|
|
+ print("\n" + "=" * 80)
|
|
|
|
|
+ print(f"[Message #{message.sequence}] {message.role.upper()}")
|
|
|
|
|
+ print("=" * 80)
|
|
|
|
|
+
|
|
|
|
|
+ # 基本信息
|
|
|
|
|
+ if message.goal_id:
|
|
|
|
|
+ print(f"Goal ID: {message.goal_id}")
|
|
|
|
|
+ if message.parent_sequence is not None:
|
|
|
|
|
+ print(f"Parent Sequence: {message.parent_sequence}")
|
|
|
|
|
+ if message.tool_call_id:
|
|
|
|
|
+ print(f"Tool Call ID: {message.tool_call_id}")
|
|
|
|
|
+
|
|
|
|
|
+ # 内容打印
|
|
|
|
|
+ if message.role == "user":
|
|
|
|
|
+ print("\n[输入内容]")
|
|
|
|
|
+ print("-" * 80)
|
|
|
|
|
+ if isinstance(message.content, str):
|
|
|
|
|
+ print(message.content)
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(_format_json(message.content))
|
|
|
|
|
+
|
|
|
|
|
+ elif message.role == "assistant":
|
|
|
|
|
+ content = message.content
|
|
|
|
|
+ if isinstance(content, dict):
|
|
|
|
|
+ text = content.get("text", "")
|
|
|
|
|
+ tool_calls = content.get("tool_calls")
|
|
|
|
|
+
|
|
|
|
|
+ if text:
|
|
|
|
|
+ print("\n[LLM 文本回复]")
|
|
|
|
|
+ print("-" * 80)
|
|
|
|
|
+ print(text)
|
|
|
|
|
+
|
|
|
|
|
+ if tool_calls:
|
|
|
|
|
+ print(f"\n[工具调用] (共 {len(tool_calls)} 个)")
|
|
|
|
|
+ print("-" * 80)
|
|
|
|
|
+ for idx, tc in enumerate(tool_calls, 1):
|
|
|
|
|
+ func = tc.get("function", {})
|
|
|
|
|
+ tool_name = func.get("name", "unknown")
|
|
|
|
|
+ tool_id = tc.get("id", "unknown")
|
|
|
|
|
+ arguments = func.get("arguments", {})
|
|
|
|
|
+
|
|
|
|
|
+ print(f"\n工具 #{idx}: {tool_name}")
|
|
|
|
|
+ print(f" Call ID: {tool_id}")
|
|
|
|
|
+ print(f" 参数:")
|
|
|
|
|
+ # 尝试解析 arguments(可能是字符串或字典)
|
|
|
|
|
+ if isinstance(arguments, str):
|
|
|
|
|
+ try:
|
|
|
|
|
+ parsed_args = json.loads(arguments)
|
|
|
|
|
+ print(_format_json(parsed_args, indent=4))
|
|
|
|
|
+ except json.JSONDecodeError:
|
|
|
|
|
+ print(f" {arguments}")
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(_format_json(arguments, indent=4))
|
|
|
|
|
+ elif isinstance(content, str):
|
|
|
|
|
+ print("\n[LLM 文本回复]")
|
|
|
|
|
+ print("-" * 80)
|
|
|
|
|
+ print(content)
|
|
|
|
|
+ else:
|
|
|
|
|
+ print("\n[内容]")
|
|
|
|
|
+ print("-" * 80)
|
|
|
|
|
+ print(_format_json(content))
|
|
|
|
|
+
|
|
|
|
|
+ if message.finish_reason:
|
|
|
|
|
+ print(f"\n完成原因: {message.finish_reason}")
|
|
|
|
|
+
|
|
|
|
|
+ elif message.role == "tool":
|
|
|
|
|
+ content = message.content
|
|
|
|
|
+ print("\n[工具执行结果]")
|
|
|
|
|
+ print("-" * 80)
|
|
|
|
|
+ if isinstance(content, dict):
|
|
|
|
|
+ tool_name = content.get("tool_name", "unknown")
|
|
|
|
|
+ result = content.get("result", content)
|
|
|
|
|
+ print(f"工具名称: {tool_name}")
|
|
|
|
|
+ print(f"\n返回结果:")
|
|
|
|
|
+ if isinstance(result, str):
|
|
|
|
|
+ print(result)
|
|
|
|
|
+ elif isinstance(result, list):
|
|
|
|
|
+ # 可能是多模态内容(包含图片)
|
|
|
|
|
+ for idx, item in enumerate(result, 1):
|
|
|
|
|
+ if isinstance(item, dict) and item.get("type") == "image_url":
|
|
|
|
|
+ print(f" [{idx}] 图片 (base64, 已省略显示)")
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(f" [{idx}] {item}")
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(_format_json(result))
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(str(content) if content is not None else "(无内容)")
|
|
|
|
|
+
|
|
|
|
|
+ elif message.role == "system":
|
|
|
|
|
+ print("\n[系统提示]")
|
|
|
|
|
+ print("-" * 80)
|
|
|
|
|
+ if isinstance(message.content, str):
|
|
|
|
|
+ print(message.content)
|
|
|
|
|
+ else:
|
|
|
|
|
+ print(_format_json(message.content))
|
|
|
|
|
+
|
|
|
|
|
+ # Token 和成本信息
|
|
|
|
|
+ if message.prompt_tokens is not None or message.completion_tokens is not None:
|
|
|
|
|
+ print("\n[Token 使用]")
|
|
|
|
|
+ print("-" * 80)
|
|
|
|
|
+ if message.prompt_tokens is not None:
|
|
|
|
|
+ print(f" 输入 Tokens: {message.prompt_tokens:,}")
|
|
|
|
|
+ if message.completion_tokens is not None:
|
|
|
|
|
+ print(f" 输出 Tokens: {message.completion_tokens:,}")
|
|
|
|
|
+ if message.reasoning_tokens is not None:
|
|
|
|
|
+ print(f" 推理 Tokens: {message.reasoning_tokens:,}")
|
|
|
|
|
+ if message.cache_creation_tokens is not None:
|
|
|
|
|
+ print(f" 缓存创建 Tokens: {message.cache_creation_tokens:,}")
|
|
|
|
|
+ if message.cache_read_tokens is not None:
|
|
|
|
|
+ print(f" 缓存读取 Tokens: {message.cache_read_tokens:,}")
|
|
|
|
|
+ if message.tokens:
|
|
|
|
|
+ print(f" 总计 Tokens: {message.tokens:,}")
|
|
|
|
|
+
|
|
|
|
|
+ if message.cost is not None:
|
|
|
|
|
+ print(f"\n[成本] ${message.cost:.6f}")
|
|
|
|
|
+
|
|
|
|
|
+ if message.duration_ms is not None:
|
|
|
|
|
+ print(f"[执行时间] {message.duration_ms}ms")
|
|
|
|
|
+
|
|
|
|
|
+ print("=" * 80 + "\n")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
# ===== 交互菜单 =====
|
|
# ===== 交互菜单 =====
|
|
|
|
|
|
|
|
def _read_multiline() -> str:
|
|
def _read_multiline() -> str:
|
|
@@ -512,7 +649,11 @@ async def main():
|
|
|
# 处理 Message 对象(执行过程)
|
|
# 处理 Message 对象(执行过程)
|
|
|
elif isinstance(item, Message):
|
|
elif isinstance(item, Message):
|
|
|
current_sequence = item.sequence
|
|
current_sequence = item.sequence
|
|
|
|
|
+
|
|
|
|
|
+ # 完整打印所有消息详情
|
|
|
|
|
+ _print_message_details(item)
|
|
|
|
|
|
|
|
|
|
+ # 保留原有的简化输出逻辑(用于最终响应)
|
|
|
if item.role == "assistant":
|
|
if item.role == "assistant":
|
|
|
content = item.content
|
|
content = item.content
|
|
|
if isinstance(content, dict):
|
|
if isinstance(content, dict):
|
|
@@ -522,25 +663,6 @@ async def main():
|
|
|
if text and not tool_calls:
|
|
if text and not tool_calls:
|
|
|
# 纯文本回复(最终响应)
|
|
# 纯文本回复(最终响应)
|
|
|
final_response = text
|
|
final_response = text
|
|
|
- print(f"\n[Response] Agent 回复:")
|
|
|
|
|
- print(text)
|
|
|
|
|
- elif text:
|
|
|
|
|
- preview = text[:150] + "..." if len(text) > 150 else text
|
|
|
|
|
- print(f"[Assistant] {preview}")
|
|
|
|
|
-
|
|
|
|
|
- if tool_calls:
|
|
|
|
|
- for tc in tool_calls:
|
|
|
|
|
- tool_name = tc.get("function", {}).get("name", "unknown")
|
|
|
|
|
- print(f"[Tool Call] 🛠️ {tool_name}")
|
|
|
|
|
-
|
|
|
|
|
- elif item.role == "tool":
|
|
|
|
|
- content = item.content
|
|
|
|
|
- if isinstance(content, dict):
|
|
|
|
|
- tool_name = content.get("tool_name", "unknown")
|
|
|
|
|
- print(f"[Tool Result] ✅ {tool_name}")
|
|
|
|
|
- if item.description:
|
|
|
|
|
- desc = item.description[:80] if len(item.description) > 80 else item.description
|
|
|
|
|
- print(f" {desc}...")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
print(f"\n执行出错: {e}")
|
|
print(f"\n执行出错: {e}")
|