Jelajahi Sumber

增加日志上传

xueyiming 2 minggu lalu
induk
melakukan
423ef8b0b0

+ 2 - 0
.env.example

@@ -43,5 +43,7 @@ ALIYUN_OSS_ACCESS_KEY_SECRET=
 ALIYUN_OSS_REGION=cn-hangzhou
 ALIYUN_OSS_BUCKET=art-pubbucket
 ALIYUN_OSS_ROOT_PREFIX=supply_agent
+# 手动上传日志专用 OSS 目录(与 Agent 自动上传 / MySQL oss_logs 路径分离)
+ALIYUN_OSS_MANUAL_LOG_PREFIX=supply_agent/manual_logs
 ALIYUN_OSS_PUBLIC_BASE_URL=http://rescdn.yishihui.com
 LOG_OSS_UPLOAD_ENABLED=true

+ 40 - 0
scripts/upload_logs_to_oss.py

@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+"""Upload a log file from logs/ to OSS (manual folder, no MySQL).
+
+Usage:
+  python scripts/upload_logs_to_oss.py run_xxx.log
+
+  # 从 Python 调用
+  from scripts.upload_logs_to_oss import main
+  oss_path = main("run_xxx.log")
+"""
+
+from __future__ import annotations
+
+import sys
+from pathlib import Path
+
+_ROOT = Path(__file__).resolve().parents[1]
+_LOGS_DIR = _ROOT / "logs"
+if str(_ROOT) not in sys.path:
+    sys.path.insert(0, str(_ROOT))
+
+from supply_infra.config import get_infra_settings
+from supply_infra.oss.client import OssClient
+
+
+def main(filename: str) -> str:
+    """Read ``logs/{filename}``, upload to OSS, return public OSS path."""
+    local_path = _LOGS_DIR / Path(filename).name
+    if not local_path.is_file():
+        raise FileNotFoundError(f"Log file not found: {local_path}")
+
+    settings = get_infra_settings()
+    client = OssClient(settings, root_prefix=settings.aliyun_oss_manual_log_prefix)
+    object_key = client.object_key(local_path.name)
+    return client.upload_file(local_path, object_key)
+
+
+if __name__ == "__main__":
+    oss_path = main("run_generate_demand_agent_20260716_160355_8f751d0d.html")
+    print(oss_path)

+ 4 - 4
supply_agent/logging/visualize.py

@@ -319,11 +319,12 @@ def _render_step_card(
     title: str,
     inner_html: str,
 ) -> str:
+    title_html = f'<div class="card-title">{title}</div>' if title else ""
     return f"""
     <article class="card card-step" id="step-{step_no}">
       <header class="card-header">
         <div class="step-num">Step {step_no}</div>
-        <div class="card-title">{title}</div>
+        {title_html}
         <div class="card-tags">
           <span class="tag">iteration {iteration}</span>
         </div>
@@ -403,7 +404,7 @@ def _render_timeline(events: list[dict[str, Any]]) -> str:
                 _render_step_card(
                     step_no,
                     _event_iteration(input_ev),
-                    "LLM 输入 · 输出",
+                    "",
                     input_html + output_html,
                 )
             )
@@ -450,7 +451,7 @@ def _nav_items(events: list[dict[str, Any]]) -> str:
     for step_no, group in enumerate(_group_visible_events(events), start=1):
         if len(group) == 2:
             ev = group[0]
-            label = "LLM 输入 · 输出"
+            label = ""
             nav_class = "nav-step-pair"
             detail = f" · iter {_event_iteration(ev)}"
         else:
@@ -867,7 +868,6 @@ def render_html(events: list[dict[str, Any]]) -> str:
 
       <section class="timeline">
         <h2 style="margin:0 0 0.5rem;font-size:1.1rem">执行时间线</h2>
-        <p class="muted" style="margin:0 0 1rem">按步骤展示:LLM 输入 → 思考/输出</p>
         {_render_timeline(events)}
       </section>
     </main>

+ 4 - 0
supply_infra/config.py

@@ -49,6 +49,10 @@ class InfraSettings(BaseSettings):
     aliyun_oss_region: str = Field(default="cn-hangzhou", alias="ALIYUN_OSS_REGION")
     aliyun_oss_bucket: str = Field(default="art-pubbucket", alias="ALIYUN_OSS_BUCKET")
     aliyun_oss_root_prefix: str = Field(default="supply_agent", alias="ALIYUN_OSS_ROOT_PREFIX")
+    aliyun_oss_manual_log_prefix: str = Field(
+        default="supply_agent/manual_logs",
+        alias="ALIYUN_OSS_MANUAL_LOG_PREFIX",
+    )
     aliyun_oss_public_base_url: str = Field(
         default="http://rescdn.yishihui.com",
         alias="ALIYUN_OSS_PUBLIC_BASE_URL",

+ 8 - 2
supply_infra/oss/client.py

@@ -12,7 +12,12 @@ from supply_infra.config import InfraSettings, get_infra_settings
 class OssClient:
     """Thin wrapper around oss2 Bucket."""
 
-    def __init__(self, settings: InfraSettings | None = None) -> None:
+    def __init__(
+        self,
+        settings: InfraSettings | None = None,
+        *,
+        root_prefix: str | None = None,
+    ) -> None:
         self.settings = settings or get_infra_settings()
         if not self.settings.aliyun_oss_access_key_id:
             raise ValueError("ALIYUN_OSS_ACCESS_KEY_ID is not configured")
@@ -25,7 +30,8 @@ class OssClient:
             self.settings.aliyun_oss_access_key_secret,
         )
         self._bucket = oss2.Bucket(auth, endpoint, self.settings.aliyun_oss_bucket)
-        self._root = self.settings.aliyun_oss_root_prefix.strip("/")
+        prefix = root_prefix if root_prefix is not None else self.settings.aliyun_oss_root_prefix
+        self._root = prefix.strip("/")
         self._public_base = self.settings.aliyun_oss_public_base_url.rstrip("/")
 
     def object_key(self, *parts: str) -> str: