test_agent_trace.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. from __future__ import annotations
  2. import json
  3. from types import SimpleNamespace
  4. from langchain_core.messages import AIMessage, ToolMessage
  5. from obagent_sdk import observe
  6. from production_build_agents.observability import global_data
  7. class _Capture:
  8. def __init__(self) -> None:
  9. self.llm: list[dict] = []
  10. self.tools: list[dict] = []
  11. def record_llm(self, **payload) -> None:
  12. self.llm.append(payload)
  13. def record_tool(self, name, args=None, result=None, **payload) -> None:
  14. self.tools.append(
  15. {
  16. "name": name,
  17. "args": args,
  18. "result": result,
  19. **payload,
  20. }
  21. )
  22. def test_agent_trace_reports_metrics_without_raw_messages(
  23. monkeypatch,
  24. ) -> None:
  25. capture = _Capture()
  26. owner = SimpleNamespace(
  27. active=True,
  28. full_capture=False,
  29. warn_once=lambda *_args, **_kwargs: None,
  30. )
  31. token = global_data._ACTIVE.set(owner)
  32. monkeypatch.setattr(observe, "current", lambda: capture)
  33. messages = [
  34. AIMessage(
  35. content="客户秘密正文",
  36. tool_calls=[
  37. {
  38. "id": "call-1",
  39. "name": "inspect_tool",
  40. "args": {
  41. "url": "https://secret.example/?sig=private",
  42. "query": "客户秘密查询",
  43. },
  44. }
  45. ],
  46. usage_metadata={
  47. "input_tokens": 10,
  48. "output_tokens": 5,
  49. "total_tokens": 15,
  50. },
  51. ),
  52. ToolMessage(
  53. content=json.dumps(
  54. {
  55. "success": True,
  56. "secret_result": "客户秘密工具结果",
  57. "_duration_ms": 20,
  58. "_operation_id": "Task1-executor-v1:1",
  59. },
  60. ensure_ascii=False,
  61. ),
  62. tool_call_id="call-1",
  63. name="inspect_tool",
  64. ),
  65. ]
  66. try:
  67. global_data.record_sanitized_agent_messages(
  68. role="executor",
  69. agent_run_id="safe-agent-run",
  70. model=SimpleNamespace(model_name="safe-model"),
  71. messages=messages,
  72. message_indexes={0, 1},
  73. )
  74. finally:
  75. global_data._ACTIVE.reset(token)
  76. encoded = json.dumps(
  77. {"llm": capture.llm, "tools": capture.tools},
  78. ensure_ascii=False,
  79. )
  80. assert capture.llm[0]["input_tokens"] == 10
  81. assert capture.llm[0]["output_tokens"] == 5
  82. assert capture.tools[0]["name"] == "inspect_tool"
  83. assert capture.tools[0]["result"]["operation_id"] == (
  84. "Task1-executor-v1:1"
  85. )
  86. assert "客户秘密正文" not in encoded
  87. assert "secret.example" not in encoded
  88. assert "客户秘密查询" not in encoded
  89. assert "客户秘密工具结果" not in encoded
  90. def test_full_capture_does_not_duplicate_sanitized_agent_trace(
  91. monkeypatch,
  92. ) -> None:
  93. capture = _Capture()
  94. owner = SimpleNamespace(
  95. active=True,
  96. full_capture=True,
  97. warn_once=lambda *_args, **_kwargs: None,
  98. )
  99. token = global_data._ACTIVE.set(owner)
  100. monkeypatch.setattr(observe, "current", lambda: capture)
  101. try:
  102. global_data.record_sanitized_agent_messages(
  103. role="executor",
  104. agent_run_id="full-agent-run",
  105. model=SimpleNamespace(model_name="full-model"),
  106. messages=[AIMessage(content="由 SDK 自动采集的真实正文")],
  107. )
  108. finally:
  109. global_data._ACTIVE.reset(token)
  110. assert capture.llm == []
  111. assert capture.tools == []