test_observability.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. """LangSmith 运行配置测试。"""
  2. from datetime import datetime, timezone
  3. from pathlib import Path
  4. from types import SimpleNamespace
  5. import unittest
  6. from unittest.mock import patch
  7. from vendor_guard.observability import (
  8. build_langsmith_client,
  9. build_run_config,
  10. get_latest_trace_url,
  11. langsmith_status,
  12. langsmith_trace_scope,
  13. )
  14. from vendor_guard.settings import Settings
  15. class ObservabilityTests(unittest.TestCase):
  16. def setUp(self) -> None:
  17. self.settings = Settings(
  18. project_root=Path("."),
  19. model_name="openai:test-model",
  20. organization_id="course-org",
  21. assistant_id="vendor-guard",
  22. environment="test",
  23. langsmith_tracing=True,
  24. langsmith_api_key="secret-value",
  25. langsmith_project="vendor-guard-test",
  26. )
  27. def test_run_config_contains_business_metadata(self) -> None:
  28. config = build_run_config(
  29. self.settings,
  30. "acme",
  31. "vendor-acme-001",
  32. run_kind="evaluation",
  33. )
  34. self.assertEqual(config["run_name"], "vendor-guard-evaluation")
  35. self.assertEqual(config["configurable"]["thread_id"], "vendor-acme-001")
  36. self.assertEqual(config["metadata"]["vendor"], "ACME")
  37. self.assertIn("vendor:ACME", config["tags"])
  38. self.assertIn("environment:test", config["tags"])
  39. def test_status_does_not_expose_api_key(self) -> None:
  40. status = langsmith_status(self.settings)
  41. self.assertIn("vendor-guard-test", status)
  42. self.assertNotIn("secret-value", status)
  43. @patch("vendor_guard.observability.Client")
  44. def test_langsmith_client_receives_key_from_settings(self, client_class) -> None:
  45. build_langsmith_client(self.settings)
  46. client_class.assert_called_once_with(
  47. api_key="secret-value",
  48. api_url=None,
  49. workspace_id=None,
  50. hide_inputs=False,
  51. hide_outputs=False,
  52. )
  53. @patch("vendor_guard.observability.tracing_context")
  54. @patch("vendor_guard.observability.build_langsmith_client")
  55. def test_trace_scope_binds_and_flushes_client(
  56. self,
  57. build_client,
  58. tracing_context,
  59. ) -> None:
  60. client = build_client.return_value
  61. with langsmith_trace_scope(self.settings) as bound_client:
  62. self.assertIs(bound_client, client)
  63. tracing_context.assert_called_once_with(
  64. enabled=True,
  65. project_name="vendor-guard-test",
  66. client=client,
  67. )
  68. client.flush.assert_called_once_with()
  69. client.close.assert_called_once_with()
  70. @patch("vendor_guard.observability.build_langsmith_client")
  71. def test_latest_trace_url_matches_thread(self, build_client) -> None:
  72. client = build_client.return_value
  73. matching_run = SimpleNamespace(
  74. name="vendor-guard-interactive",
  75. start_time=datetime(2026, 7, 21, tzinfo=timezone.utc),
  76. extra={"metadata": {"thread_id": "vendor-acme-001"}},
  77. )
  78. other_run = SimpleNamespace(
  79. name="vendor-guard-interactive",
  80. start_time=datetime(2026, 7, 20, tzinfo=timezone.utc),
  81. extra={"metadata": {"thread_id": "vendor-other-001"}},
  82. )
  83. client.list_runs.return_value = [other_run, matching_run]
  84. client.get_run_url.return_value = "https://smith.example/run/acme"
  85. url = get_latest_trace_url(
  86. self.settings,
  87. "vendor-acme-001",
  88. client=client,
  89. )
  90. self.assertEqual(url, "https://smith.example/run/acme")
  91. client.get_run_url.assert_called_once_with(
  92. run=matching_run,
  93. project_name="vendor-guard-test",
  94. )
  95. client.close.assert_not_called()
  96. @patch("vendor_guard.observability.build_langsmith_client")
  97. def test_latest_trace_url_is_disabled_without_tracing(self, build_client) -> None:
  98. settings = Settings(project_root=Path("."), langsmith_tracing=False)
  99. self.assertIsNone(get_latest_trace_url(settings, "vendor-acme-001"))
  100. build_client.assert_not_called()
  101. if __name__ == "__main__":
  102. unittest.main()