publish.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. """Publish agent run logs: HTML visualize → OSS upload → MySQL record."""
  2. from __future__ import annotations
  3. import logging
  4. from pathlib import Path
  5. from typing import TYPE_CHECKING
  6. from supply_agent.logging.logger import slugify_agent_name
  7. from supply_agent.logging.visualize import generate_visualization
  8. from supply_infra.config import get_infra_settings
  9. from supply_infra.db.repositories.oss_log_repo import OssLogRepository
  10. from supply_infra.db.session import get_session
  11. from supply_infra.oss.client import OssClient
  12. if TYPE_CHECKING:
  13. from supply_agent.logging.logger import AgentLogger
  14. _log = logging.getLogger("supply_agent.publish")
  15. def publish_run_artifacts(agent_logger: AgentLogger) -> str | None:
  16. """
  17. After a run finishes: generate HTML, upload .log/.jsonl/.html to OSS,
  18. and insert a row into ``oss_logs``.
  19. Returns the public HTML URL, or None if skipped / failed.
  20. Failures are logged and do not raise (so agent runs are not broken).
  21. """
  22. settings = get_infra_settings()
  23. if not settings.log_oss_upload_enabled:
  24. return None
  25. if not settings.aliyun_oss_access_key_id or not settings.aliyun_oss_access_key_secret:
  26. _log.debug("OSS credentials missing; skip log publish")
  27. return None
  28. log_file = agent_logger.log_file
  29. jsonl_file = agent_logger.jsonl_file
  30. if not log_file or not log_file.exists():
  31. _log.warning("No log file to publish")
  32. return None
  33. try:
  34. source = jsonl_file if jsonl_file and jsonl_file.exists() else log_file
  35. html_path = generate_visualization(source)
  36. agent_slug = slugify_agent_name(agent_logger.agent_name) or "agent"
  37. log_name = html_path.stem
  38. client = OssClient(settings)
  39. files = [p for p in (log_file, jsonl_file, html_path) if p and Path(p).exists()]
  40. html_url: str | None = None
  41. for path in files:
  42. key = client.object_key(agent_slug, Path(path).name)
  43. url = client.upload_file(path, key)
  44. if Path(path).suffix.lower() == ".html":
  45. html_url = url
  46. _log.info("Uploaded %s → %s", path.name, url)
  47. if not html_url:
  48. raise RuntimeError("HTML was not uploaded")
  49. with get_session() as session:
  50. OssLogRepository(session).create(
  51. log_name=log_name,
  52. agent_name=agent_logger.agent_name or agent_slug,
  53. oss_path=html_url,
  54. )
  55. _log.info("oss_logs saved: log_name=%s url=%s", log_name, html_url)
  56. print(f"[publish] 可视化已上传: {html_url}")
  57. return html_url
  58. except Exception:
  59. _log.exception("Failed to publish run artifacts")
  60. print("[publish] 日志上传/入库失败,详见日志;不影响本次 Agent 结果")
  61. return None