| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- """工具调用日志的通用封装。"""
- from __future__ import annotations
- import json
- from typing import Any, Dict
- from .log_capture import log, log_fold
- def _pretty_json_if_possible(text: str) -> str:
- """如果文本是合法 JSON,则返回带缩进的可读格式;否则原样返回。"""
- raw = (text or "").strip()
- if not raw:
- return text
- if not (raw.startswith("{") or raw.startswith("[")):
- return text
- try:
- parsed = json.loads(raw)
- except Exception:
- return text
- return json.dumps(parsed, ensure_ascii=False, indent=2)
- def _truncate_deep(obj: Any, str_limit: int = 2000) -> Any:
- """递归遍历对象,对超长字符串做截断,其余结构原样保留。"""
- if isinstance(obj, str):
- return obj if len(obj) <= str_limit else obj[:str_limit] + f"...(truncated, total {len(obj)} chars)"
- if isinstance(obj, dict):
- return {k: _truncate_deep(v, str_limit) for k, v in obj.items()}
- if isinstance(obj, list):
- return [_truncate_deep(item, str_limit) for item in obj]
- return obj
- def _structure_metadata(md: Dict[str, Any], body_limit: int = 200) -> Dict[str, Any]:
- """对 metadata 做结构化精简,剥离 raw_data / 完整正文等大字段。
- - 含 article_info 的结果:提取标题、统计、正文预览,丢弃 raw HTML / 图片列表。
- - 含 account_info 的结果:保留账号关键字段。
- - 含 search_results 的结果:每条只保留标题和 URL。
- - 其他情况:递归截断超长字符串。
- """
- # --- 文章详情 ---
- article_info = md.get("article_info")
- if isinstance(article_info, dict):
- body = str(article_info.get("body_text", "") or "")
- body_preview = body[:body_limit] + "..." if len(body) > body_limit else body
- # 去掉图片标记行
- body_preview = "\n".join(
- line for line in body_preview.splitlines()
- if not line.strip().startswith("[image:")
- )
- images = article_info.get("image_url_list") or []
- return {
- "article_info": {
- "title": article_info.get("title", ""),
- "content_link": article_info.get("content_link", ""),
- "publish_timestamp": article_info.get("publish_timestamp"),
- "statistics": {
- "view_count": article_info.get("view_count"),
- "like_count": article_info.get("like_count"),
- "share_count": article_info.get("share_count"),
- "looking_count": article_info.get("looking_count"),
- "comment_count": article_info.get("comment_count"),
- "collect_count": article_info.get("collect_count"),
- },
- "is_original": article_info.get("is_original", False),
- "image_count": len(images),
- "body_length": len(body),
- "body_preview": body_preview,
- }
- }
- # --- 账号信息 ---
- account_info = md.get("account_info")
- if isinstance(account_info, dict):
- return {
- "account_info": {
- "account_name": account_info.get("account_name", ""),
- "wx_gh": account_info.get("wx_gh", ""),
- "channel_account_id": account_info.get("channel_account_id", ""),
- }
- }
- # --- 搜索结果列表 ---
- search_results = md.get("search_results")
- if isinstance(search_results, list):
- brief = [
- {"title": item.get("title", ""), "url": item.get("url", "")}
- for item in search_results[:20]
- ]
- return {"search_results": brief, "total": len(search_results)}
- # --- 兜底:递归截断 ---
- return _truncate_deep(md)
- def format_tool_result_for_log(result: Any) -> str:
- """将 ToolResult 或普通字符串格式化为可写入日志的文本。
- 对文章详情类结果,输出结构化摘要(标题/统计/正文预览),
- 剥离 raw_data 和完整正文,避免日志被大段内容淹没。
- """
- if result is None:
- return ""
- if isinstance(result, str):
- s = result
- return s if len(s) <= 8000 else s[:8000] + "\n...(truncated)"
- title = getattr(result, "title", "") or ""
- output = getattr(result, "output", None) or ""
- err = getattr(result, "error", None)
- payload: Dict[str, Any] = {"title": title, "output": output}
- if err:
- payload["error"] = err
- md = getattr(result, "metadata", None)
- if isinstance(md, dict) and md:
- payload["metadata"] = _structure_metadata(md)
- return json.dumps(payload, ensure_ascii=False)
- def log_tool_call(tool_name: str, params: Dict[str, Any], result: str) -> None:
- """以折叠块结构化输出工具调用参数与返回内容。"""
- with log_fold(f"🔧 {tool_name}"):
- with log_fold("📥 调用参数"):
- log(json.dumps(params, ensure_ascii=False, indent=2))
- with log_fold("📤 返回内容"):
- log(_pretty_json_if_possible(result))
|