test_agent_trace.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  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. warn_once=lambda *_args, **_kwargs: None,
  29. )
  30. token = global_data._ACTIVE.set(owner)
  31. monkeypatch.setattr(observe, "current", lambda: capture)
  32. messages = [
  33. AIMessage(
  34. content="客户秘密正文",
  35. tool_calls=[
  36. {
  37. "id": "call-1",
  38. "name": "inspect_tool",
  39. "args": {
  40. "url": "https://secret.example/?sig=private",
  41. "query": "客户秘密查询",
  42. },
  43. }
  44. ],
  45. usage_metadata={
  46. "input_tokens": 10,
  47. "output_tokens": 5,
  48. "total_tokens": 15,
  49. },
  50. ),
  51. ToolMessage(
  52. content=json.dumps(
  53. {
  54. "success": True,
  55. "secret_result": "客户秘密工具结果",
  56. "_duration_ms": 20,
  57. "_operation_id": "Task1-executor-v1:1",
  58. },
  59. ensure_ascii=False,
  60. ),
  61. tool_call_id="call-1",
  62. name="inspect_tool",
  63. ),
  64. ]
  65. try:
  66. global_data.record_sanitized_agent_messages(
  67. role="executor",
  68. agent_run_id="safe-agent-run",
  69. model=SimpleNamespace(model_name="safe-model"),
  70. messages=messages,
  71. message_indexes={0, 1},
  72. )
  73. finally:
  74. global_data._ACTIVE.reset(token)
  75. encoded = json.dumps(
  76. {"llm": capture.llm, "tools": capture.tools},
  77. ensure_ascii=False,
  78. )
  79. assert capture.llm[0]["input_tokens"] == 10
  80. assert capture.llm[0]["output_tokens"] == 5
  81. assert capture.tools[0]["name"] == "inspect_tool"
  82. assert capture.tools[0]["result"]["operation_id"] == (
  83. "Task1-executor-v1:1"
  84. )
  85. assert "客户秘密正文" not in encoded
  86. assert "secret.example" not in encoded
  87. assert "客户秘密查询" not in encoded
  88. assert "客户秘密工具结果" not in encoded