| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- from __future__ import annotations
- import json
- from pathlib import Path
- from types import SimpleNamespace
- from obagent_sdk import observe
- from obagent_sdk.integrations import langgraph
- from production_build_agents.observability.global_data import (
- GlobalDataObservation,
- start_graph_node_observation,
- )
- from production_build_agents.observability import global_data
- from production_build_agents.observability import collector as collector_module
- class _RunHandle:
- def __init__(self) -> None:
- self.finished: list[dict] = []
- def finish(self, **payload) -> None:
- self.finished.append(payload)
- class _RunManager:
- def __init__(self, handle: _RunHandle) -> None:
- self.handle = handle
- self.exits: list[tuple] = []
- def __enter__(self) -> _RunHandle:
- return self.handle
- def __exit__(self, *args):
- self.exits.append(args)
- if args[1] is not None:
- raise args[1]
- return False
- class _ModuleHandle:
- def __init__(self) -> None:
- self.declared: list[dict] = []
- self.outputs: list[tuple[object, dict]] = []
- self.notes: list[tuple[str, dict]] = []
- def declare(self, **payload) -> None:
- self.declared.append(payload)
- def set_output(self, output, **payload) -> None:
- self.outputs.append((output, payload))
- def record_note(self, text: str, **payload) -> None:
- self.notes.append((text, payload))
- def test_global_data_context_forces_sanitized_manual_collection(
- monkeypatch,
- tmp_path: Path,
- ) -> None:
- captured: dict = {}
- handle = _RunHandle()
- manager = _RunManager(handle)
- def fake_run(**kwargs):
- captured.update(kwargs)
- return manager
- monkeypatch.setattr(observe, "run", fake_run)
- monkeypatch.setattr(
- langgraph,
- "graph_spec",
- lambda _graph: {"nodes": [{"key": "preprocess"}], "edges": []},
- )
- settings = SimpleNamespace(
- configure_sdk=lambda: True,
- project="safe-project",
- project_name="安全项目",
- capture_mode="redacted",
- )
- result = {
- "run_id": "safe-run",
- "protocol_version": "0.3",
- "status": "COMPLETED",
- "phase": "FINALIZE",
- "input_path": "/private/客户标题.json",
- "output_dir": str(tmp_path),
- "event_log": ["客户正文"],
- }
- with GlobalDataObservation(
- project_root=tmp_path,
- thread_id="safe-run",
- input_sha256="a" * 64,
- protocol_version="0.3",
- execution_mode="fresh",
- graph=object(),
- settings=settings,
- ) as observation:
- observation.finish(result, graph_invoked=True)
- encoded = json.dumps(
- {"run": captured, "finish": handle.finished},
- ensure_ascii=False,
- )
- assert captured["auto_collect"] is False
- assert captured["round_anchor"] == {
- "in": "run",
- "on": ["prepare_next_task"],
- }
- assert captured["payload"]["input_sha256"] == "a" * 64
- assert captured["meta"]["capture_mode"] == "redacted_allowlist_v1"
- assert handle.finished[0]["ok"] is True
- assert "/private/客户标题.json" not in encoded
- assert "客户正文" not in encoded
- def test_global_data_context_full_capture_reports_real_records(
- monkeypatch,
- tmp_path: Path,
- ) -> None:
- captured: dict = {}
- collector_calls: list[tuple[str, object]] = []
- handle = _RunHandle()
- manager = _RunManager(handle)
- input_path = tmp_path / "production_final.json"
- delivery_path = tmp_path / "GlobalDataStageDelivery.v1.json"
- input_path.write_text(
- json.dumps(
- {
- "title": "客户真实标题",
- "reference_url": "https://internal.example/input.png",
- },
- ensure_ascii=False,
- ),
- encoding="utf-8",
- )
- delivery_path.write_text(
- json.dumps(
- {
- "summary": "完整交付正文",
- "media_url": "https://internal.example/output.mp4",
- },
- ensure_ascii=False,
- ),
- encoding="utf-8",
- )
- def fake_run(**kwargs):
- captured.update(kwargs)
- return manager
- monkeypatch.setattr(observe, "run", fake_run)
- monkeypatch.setattr(
- collector_module,
- "_start_business_collector",
- lambda run_handle: (
- collector_calls.append(("start", run_handle))
- or "collector-token"
- ),
- )
- monkeypatch.setattr(
- collector_module,
- "_stop_business_collector",
- lambda token: collector_calls.append(("stop", token)),
- )
- monkeypatch.setattr(
- langgraph,
- "graph_spec",
- lambda _graph: {"nodes": [{"key": "preprocess"}], "edges": []},
- )
- settings = SimpleNamespace(
- configure_sdk=lambda: True,
- project="full-project",
- project_name="完整观测项目",
- capture_mode="full",
- )
- result = {
- "run_id": "full-run",
- "protocol_version": "0.3",
- "status": "COMPLETED",
- "phase": "FINALIZE",
- "input_path": str(input_path),
- "output_dir": str(tmp_path),
- "global_data_delivery_path": str(delivery_path),
- "event_log": ["完整事件正文"],
- }
- with GlobalDataObservation(
- project_root=tmp_path,
- thread_id="full-run",
- input_sha256="b" * 64,
- protocol_version="0.3",
- execution_mode="fresh",
- graph=object(),
- input_path=input_path,
- parent_run_id="pipeline-run",
- parent_inst_id="global-stage",
- pipeline_round_id="round-1",
- settings=settings,
- ) as observation:
- observation.finish(result, graph_invoked=True)
- encoded = json.dumps(
- {"run": captured, "finish": handle.finished},
- ensure_ascii=False,
- )
- assert captured["auto_collect"] is False
- assert captured["round_anchor"] == {
- "in": "run",
- "on": ["prepare_next_task"],
- }
- assert captured["parent_run_id"] == "pipeline-run"
- assert captured["parent_inst_id"] == "global-stage"
- assert captured["payload"]["pipeline_round_id"] == "round-1"
- assert collector_calls == [
- ("start", handle),
- ("stop", "collector-token"),
- ]
- assert captured["meta"]["capture_mode"] == "full_v1"
- assert captured["payload"]["input"]["content"]["title"] == (
- "客户真实标题"
- )
- assert handle.finished[0]["ok"] is True
- assert "客户真实标题" in encoded
- assert "完整交付正文" not in encoded
- assert "https://internal.example/output.mp4" not in encoded
- assert handle.finished[0]["final_output"]["结论"] == "已完成"
- assert "state_after" not in encoded
- def test_full_node_capture_expands_records_and_enables_media_scan(
- monkeypatch,
- tmp_path: Path,
- ) -> None:
- task_path = tmp_path / "Task1.TaskPackage.v1.json"
- delivery_path = tmp_path / "Task1.ExecutorDelivery.v1.json"
- task_path.write_text(
- json.dumps(
- {"task_id": "Task1", "plan_version": 1},
- ensure_ascii=False,
- ),
- encoding="utf-8",
- )
- delivery_path.write_text(
- json.dumps(
- {
- "text": "完整工具交付",
- "image_url": "https://internal.example/generated.png",
- },
- ensure_ascii=False,
- ),
- encoding="utf-8",
- )
- handle = _ModuleHandle()
- manager = _RunManager(handle)
- monkeypatch.setattr(observe, "module", lambda *_args, **_kwargs: manager)
- owner = SimpleNamespace(
- active=True,
- full_capture=True,
- warn_once=lambda *_args, **_kwargs: None,
- )
- token = global_data._ACTIVE.set(owner)
- try:
- observation = start_graph_node_observation(
- "execute_task",
- {
- "run_id": "full-run",
- "current_task_path": str(task_path),
- },
- )
- observation.finish(
- {
- "current_executor_delivery_path": str(delivery_path),
- "status": "RUNNING",
- "phase": "VALIDATE_TASK",
- "event_log": ["Executor 完整事件"],
- }
- )
- finally:
- global_data._ACTIVE.reset(token)
- input_blocks = handle.declared[0]["blocks"]
- assert [block.key for block in input_blocks] == [
- "execute_task_identity",
- "task_package",
- ]
- assert "Task:Task1" in input_blocks[1].value
- assert not input_blocks[1].value.startswith("{")
- assert "详情" not in handle.outputs[0][0]
- assert "完整工具交付" in handle.notes[0][0]
- assert handle.notes[0][1]["label"] == "正式记录详情"
- assert handle.outputs[0][0]["执行结果"] == "Executor 完整事件"
- assert "state_after" not in handle.outputs[0][0]
- assert handle.outputs[0][1]["images"] == "auto"
|