Преглед изворни кода

可观测性:仅允许正式 Run 显式开启上报

增加 OBAGENT_REPORT_THIS_RUN 进程级授权和 full/redacted 采集模式。\n\n显式授权故意不从项目 .env 读取,避免测试、回放和临时脚本意外连接远端观测台。
SamLee пре 2 дана
родитељ
комит
52ca392e77
3 измењених фајлова са 59 додато и 10 уклоњено
  1. 15 0
      .env.example
  2. 22 9
      production_build_agents/observability/config.py
  3. 22 1
      tests/observability/test_config.py

+ 15 - 0
.env.example

@@ -2,6 +2,21 @@
 LANGSMITH_TRACING=false
 LANGSMITH_TRACING=false
 LANGSMITH_PROJECT=video-production-global-data
 LANGSMITH_PROJECT=video-production-global-data
 
 
+# 内网 obagent 观测台。开启后默认完整上报模型消息、reasoning、工具参数/
+# 结果、Global Data State 及正式 JSON/Markdown 记录。
+# 默认禁止上报。只有在正式 Run 的当前命令中显式设置
+# OBAGENT_REPORT_THIS_RUN=true 才会连接观测台;该开关不会从 .env 读取。
+OBAGENT_REPORT_THIS_RUN=false
+OBAGENT_ENDPOINT=http://8.147.104.190:8931
+OBAGENT_PROJECT=video_image_production_build
+OBAGENT_PROJECT_NAME=视频图片制作闭环
+OBAGENT_API_KEY=
+OBAGENT_CAPTURE_MODE=full
+OBAGENT_TIMEOUT=10
+OBAGENT_BATCH_SIZE=200
+OBAGENT_FLUSH_INTERVAL=0.5
+OBAGENT_WAL_DIR=
+
 # 需要在 LangSmith 中查看嵌套模型/工具 Trace 时:
 # 需要在 LangSmith 中查看嵌套模型/工具 Trace 时:
 # 1. 填入自己的 Key;
 # 1. 填入自己的 Key;
 # 2. 将 LANGSMITH_TRACING 改为 true。
 # 2. 将 LANGSMITH_TRACING 改为 true。

+ 22 - 9
production_build_agents/observability/config.py

@@ -1,4 +1,4 @@
-"""obagent SDK 的显式、安全配置。"""
+"""obagent SDK 的显式运行时配置。"""
 
 
 from __future__ import annotations
 from __future__ import annotations
 
 
@@ -6,7 +6,7 @@ import os
 import warnings
 import warnings
 from dataclasses import dataclass
 from dataclasses import dataclass
 from pathlib import Path
 from pathlib import Path
-from typing import Mapping
+from typing import Literal, Mapping
 
 
 from dotenv import dotenv_values
 from dotenv import dotenv_values
 
 
@@ -55,6 +55,11 @@ def _positive_int(value: str, *, default: int) -> int:
     return parsed if parsed > 0 else default
     return parsed if parsed > 0 else default
 
 
 
 
+def _capture_mode(value: str) -> Literal["full", "redacted"]:
+    normalized = value.strip().lower()
+    return "redacted" if normalized == "redacted" else "full"
+
+
 @dataclass(frozen=True)
 @dataclass(frozen=True)
 class ObservabilitySettings:
 class ObservabilitySettings:
     """只承载观测基础设施配置,不进入业务合同。"""
     """只承载观测基础设施配置,不进入业务合同。"""
@@ -68,6 +73,7 @@ class ObservabilitySettings:
     batch_size: int = 200
     batch_size: int = 200
     flush_interval: float = 0.5
     flush_interval: float = 0.5
     wal_dir: str = ""
     wal_dir: str = ""
+    capture_mode: Literal["full", "redacted"] = "full"
 
 
     @classmethod
     @classmethod
     def load(
     def load(
@@ -76,7 +82,11 @@ class ObservabilitySettings:
         project_root: Path | None = None,
         project_root: Path | None = None,
         environ: Mapping[str, str] | None = None,
         environ: Mapping[str, str] | None = None,
     ) -> "ObservabilitySettings":
     ) -> "ObservabilitySettings":
-        """读取 ``OBAGENT_*``;不把 ``.env`` 的其他变量注入进程。"""
+        """读取观测配置;只有当前进程的一次性授权可以开启上报。
+
+        ``OBAGENT_REPORT_THIS_RUN`` 故意不从项目 ``.env`` 读取,避免测试、
+        回放和临时脚本仅因共享配置存在就自动写入远端观测台。
+        """
 
 
         env = environ if environ is not None else os.environ
         env = environ if environ is not None else os.environ
         values: Mapping[str, object] = {}
         values: Mapping[str, object] = {}
@@ -89,12 +99,7 @@ class ObservabilitySettings:
                     values = {}
                     values = {}
 
 
         enabled = _boolean(
         enabled = _boolean(
-            _first(
-                "OBAGENT_ENABLED",
-                environ=env,
-                dotenv=values,
-                default="false",
-            ),
+            env.get("OBAGENT_REPORT_THIS_RUN", "false"),
             default=False,
             default=False,
         )
         )
         return cls(
         return cls(
@@ -156,6 +161,14 @@ class ObservabilitySettings:
                 dotenv=values,
                 dotenv=values,
                 default="",
                 default="",
             ).strip(),
             ).strip(),
+            capture_mode=_capture_mode(
+                _first(
+                    "OBAGENT_CAPTURE_MODE",
+                    environ=env,
+                    dotenv=values,
+                    default="full",
+                )
+            ),
         )
         )
 
 
     def configure_sdk(self) -> bool:
     def configure_sdk(self) -> bool:

+ 22 - 1
tests/observability/test_config.py

@@ -16,18 +16,20 @@ def test_observability_is_opt_in() -> None:
 
 
     assert settings.enabled is False
     assert settings.enabled is False
     assert settings.endpoint == DEFAULT_ENDPOINT
     assert settings.endpoint == DEFAULT_ENDPOINT
+    assert settings.capture_mode == "full"
 
 
 
 
 def test_environment_overrides_safe_defaults() -> None:
 def test_environment_overrides_safe_defaults() -> None:
     settings = ObservabilitySettings.load(
     settings = ObservabilitySettings.load(
         project_root=Path("/missing"),
         project_root=Path("/missing"),
         environ={
         environ={
-            "OBAGENT_ENABLED": "true",
+            "OBAGENT_REPORT_THIS_RUN": "true",
             "OBAGENT_ENDPOINT": "http://127.0.0.1:8931",
             "OBAGENT_ENDPOINT": "http://127.0.0.1:8931",
             "OBAGENT_PROJECT": "test_project",
             "OBAGENT_PROJECT": "test_project",
             "OBAGENT_TIMEOUT": "2.5",
             "OBAGENT_TIMEOUT": "2.5",
             "OBAGENT_BATCH_SIZE": "10",
             "OBAGENT_BATCH_SIZE": "10",
             "OBAGENT_FLUSH_INTERVAL": "0.2",
             "OBAGENT_FLUSH_INTERVAL": "0.2",
+            "OBAGENT_CAPTURE_MODE": "redacted",
         },
         },
     )
     )
 
 
@@ -37,3 +39,22 @@ def test_environment_overrides_safe_defaults() -> None:
     assert settings.timeout == 2.5
     assert settings.timeout == 2.5
     assert settings.batch_size == 10
     assert settings.batch_size == 10
     assert settings.flush_interval == 0.2
     assert settings.flush_interval == 0.2
+    assert settings.capture_mode == "redacted"
+
+
+def test_legacy_enabled_flag_cannot_authorize_a_run() -> None:
+    settings = ObservabilitySettings.load(
+        project_root=Path("/missing"),
+        environ={"OBAGENT_ENABLED": "true"},
+    )
+
+    assert settings.enabled is False
+
+
+def test_unknown_capture_mode_falls_back_to_full() -> None:
+    settings = ObservabilitySettings.load(
+        project_root=Path("/missing"),
+        environ={"OBAGENT_CAPTURE_MODE": "anything"},
+    )
+
+    assert settings.capture_mode == "full"