test_model_config.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. from __future__ import annotations
  2. import os
  3. import unittest
  4. from pathlib import Path
  5. from unittest.mock import patch
  6. from production_build_agents.agents.model_config import (
  7. create_configured_chat_model,
  8. )
  9. from production_build_agents.agents.executor.agent import (
  10. create_real_executor_model,
  11. )
  12. from production_build_agents.agents.planner.agent import (
  13. create_real_planner_model,
  14. )
  15. from production_build_agents.agents.validator.task_agent import (
  16. create_real_validator_model,
  17. )
  18. class _ConfigurationError(RuntimeError):
  19. pass
  20. class ModelConfigTest(unittest.TestCase):
  21. @patch("production_build_agents.agents.model_config.init_chat_model")
  22. @patch("production_build_agents.agents.model_config.load_dotenv")
  23. def test_uses_root_env_and_role_fallback(
  24. self,
  25. load_dotenv,
  26. init_chat_model,
  27. ) -> None:
  28. configured_model = object()
  29. init_chat_model.return_value = configured_model
  30. environment = {
  31. "PLANNER_MODEL": "planner-model",
  32. "PLANNER_MODEL_PROVIDER": "openai",
  33. "PLANNER_API_KEY": "test-key",
  34. "PLANNER_BASE_URL": "https://example.test/v1",
  35. "PLANNER_ENABLE_THINKING": "true",
  36. }
  37. with patch.dict(os.environ, environment, clear=True):
  38. actual = create_configured_chat_model(
  39. roles=("EXECUTOR", "PLANNER"),
  40. error_type=_ConfigurationError,
  41. missing_message="missing",
  42. )
  43. project_root = Path(__file__).resolve().parents[2]
  44. load_dotenv.assert_called_once_with(
  45. project_root / ".env",
  46. override=False,
  47. )
  48. init_chat_model.assert_called_once_with(
  49. "planner-model",
  50. model_provider="openai",
  51. temperature=0,
  52. timeout=180.0,
  53. max_retries=1,
  54. api_key="test-key",
  55. base_url="https://example.test/v1",
  56. extra_body={"enable_thinking": True},
  57. )
  58. self.assertIs(actual, configured_model)
  59. @patch("production_build_agents.agents.model_config.load_dotenv")
  60. def test_missing_model_uses_role_error(self, load_dotenv) -> None:
  61. with patch.dict(os.environ, {}, clear=True):
  62. with self.assertRaisesRegex(_ConfigurationError, "missing model"):
  63. create_configured_chat_model(
  64. roles=("VALIDATOR", "EXECUTOR", "PLANNER"),
  65. error_type=_ConfigurationError,
  66. missing_message="missing model",
  67. )
  68. load_dotenv.assert_called_once()
  69. @patch("production_build_agents.agents.model_config.init_chat_model")
  70. @patch(
  71. "production_build_agents.observability.context_tracking."
  72. "observed_http_clients"
  73. )
  74. def test_full_observation_adds_transport_client_without_changing_retries(
  75. self,
  76. observed_http_clients,
  77. init_chat_model,
  78. ) -> None:
  79. transport_client = object()
  80. observed_http_clients.return_value = {
  81. "http_client": transport_client,
  82. }
  83. with patch.dict(
  84. os.environ,
  85. {
  86. "PLANNER_MODEL": "planner-model",
  87. "PLANNER_MODEL_PROVIDER": "openai",
  88. "PLANNER_MAX_RETRIES": "3",
  89. },
  90. clear=True,
  91. ):
  92. create_configured_chat_model(
  93. roles=("PLANNER",),
  94. error_type=_ConfigurationError,
  95. missing_message="missing",
  96. )
  97. observed_http_clients.assert_called_once_with("openai")
  98. self.assertEqual(
  99. init_chat_model.call_args.kwargs["max_retries"],
  100. 3,
  101. )
  102. self.assertIs(
  103. init_chat_model.call_args.kwargs["http_client"],
  104. transport_client,
  105. )
  106. self.assertEqual(
  107. init_chat_model.call_args.kwargs["http_socket_options"],
  108. (),
  109. )
  110. @patch("production_build_agents.agents.model_config.init_chat_model")
  111. def test_each_agent_loads_its_model_from_root_env(
  112. self,
  113. init_chat_model,
  114. ) -> None:
  115. project_root = Path(__file__).resolve().parents[2]
  116. cases = (
  117. ("PLANNER", create_real_planner_model),
  118. ("EXECUTOR", create_real_executor_model),
  119. ("VALIDATOR", create_real_validator_model),
  120. )
  121. for role, factory in cases:
  122. with self.subTest(role=role), patch.dict(
  123. os.environ,
  124. {f"{role}_MODEL": f"{role.lower()}-model"},
  125. clear=True,
  126. ), patch(
  127. "production_build_agents.agents.model_config.load_dotenv"
  128. ) as load_dotenv:
  129. factory()
  130. load_dotenv.assert_called_once_with(
  131. project_root / ".env",
  132. override=False,
  133. )
  134. self.assertEqual(init_chat_model.call_count, 3)