presentation.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. """Notebook-oriented presentation helpers for analysis results."""
  2. from __future__ import annotations
  3. import html
  4. from typing import Iterable
  5. from .agent_runner import AgentStepTrace, AnalysisRunResult
  6. def _tool_label(tool_name: str | None) -> str:
  7. if tool_name == "PythonInterpreterTool":
  8. return "本地 Python 分析"
  9. if tool_name == "TavilySearchTool":
  10. return "联网背景检索"
  11. if tool_name:
  12. return tool_name
  13. return "报告收敛"
  14. def _status_label(status: str) -> str:
  15. mapping = {
  16. "success": "成功",
  17. "partial": "部分完成",
  18. "error": "失败",
  19. "unknown": "未知",
  20. }
  21. return mapping.get(status, status)
  22. def _escape(value: object) -> str:
  23. return html.escape(str(value))
  24. def _trace_short_observation(trace: AgentStepTrace) -> str:
  25. if trace.observation_preview:
  26. return trace.observation_preview
  27. if trace.observation:
  28. return " ".join(trace.observation.split())[:220]
  29. return ""
  30. def _iter_failed_traces(step_traces: Iterable[AgentStepTrace]) -> list[AgentStepTrace]:
  31. failed = []
  32. for trace in step_traces:
  33. observation = trace.observation or ""
  34. if trace.tool_status == "error" or "Traceback" in observation:
  35. failed.append(trace)
  36. return failed
  37. def render_trace_table(result: AnalysisRunResult):
  38. """Render the agent reasoning trace as notebook-friendly HTML."""
  39. from IPython.display import HTML
  40. rows = []
  41. for trace in result.step_traces:
  42. if trace.action == "call_tool":
  43. stage = f"{_tool_label(trace.tool_name)} ({trace.tool_name})"
  44. else:
  45. stage = "最终报告"
  46. rows.append(
  47. """
  48. <tr>
  49. <td style="border:1px solid #d1d5db; padding:8px; vertical-align:top;">{step}</td>
  50. <td style="border:1px solid #d1d5db; padding:8px; vertical-align:top;">{stage}</td>
  51. <td style="border:1px solid #d1d5db; padding:8px; vertical-align:top;">{decision}</td>
  52. <td style="border:1px solid #d1d5db; padding:8px; vertical-align:top;">{status}</td>
  53. <td style="border:1px solid #d1d5db; padding:8px; vertical-align:top;">{observation}</td>
  54. <td style="border:1px solid #d1d5db; padding:8px; vertical-align:top;">{notes}</td>
  55. </tr>
  56. """.format(
  57. step=_escape(trace.step_index),
  58. stage=_escape(stage),
  59. decision=_escape(trace.decision or trace.action),
  60. status=_escape(_status_label(trace.tool_status)),
  61. observation=_escape(_trace_short_observation(trace) or "无"),
  62. notes=_escape(trace.summary or trace.parse_error or "无"),
  63. )
  64. )
  65. html_content = """
  66. <h2>Agent 推理轨迹表</h2>
  67. <table style="width:100%; border-collapse:collapse; font-size:14px;">
  68. <thead>
  69. <tr style="background:#f3f4f6;">
  70. <th style="border:1px solid #d1d5db; padding:8px;">Step</th>
  71. <th style="border:1px solid #d1d5db; padding:8px;">Stage / Tool</th>
  72. <th style="border:1px solid #d1d5db; padding:8px;">Decision</th>
  73. <th style="border:1px solid #d1d5db; padding:8px;">Status</th>
  74. <th style="border:1px solid #d1d5db; padding:8px;">Short Observation</th>
  75. <th style="border:1px solid #d1d5db; padding:8px;">Notes</th>
  76. </tr>
  77. </thead>
  78. <tbody>
  79. {rows}
  80. </tbody>
  81. </table>
  82. """.format(rows="".join(rows))
  83. return HTML(html_content)
  84. def render_full_report(result: AnalysisRunResult):
  85. """Render the full Markdown report without relying on plain print()."""
  86. from IPython.display import Markdown
  87. return Markdown("## 完整报告正文\n\n" + result.report_markdown)
  88. def render_diagnostics(result: AnalysisRunResult):
  89. """Render expandable diagnostics with full observations and tracebacks."""
  90. from IPython.display import HTML
  91. failed_traces = _iter_failed_traces(result.step_traces)
  92. if not failed_traces:
  93. return HTML("<h2>错误与诊断详情</h2><p>本次运行无工具级异常。</p>")
  94. details_blocks = []
  95. for trace in failed_traces:
  96. title = f"Step {trace.step_index} Traceback"
  97. body = _escape(trace.observation or trace.parse_error or "No diagnostic text available.")
  98. details_blocks.append(
  99. f"""
  100. <details style="margin-bottom:12px;">
  101. <summary style="cursor:pointer; font-weight:600;">{_escape(title)}</summary>
  102. <pre style="white-space:pre-wrap; background:#111827; color:#f9fafb; padding:12px; border-radius:8px; margin-top:8px;">{body}</pre>
  103. </details>
  104. """
  105. )
  106. return HTML("<h2>错误与诊断详情</h2>" + "".join(details_blocks))