test_config.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. import os
  2. import pytest
  3. from opendevin.core.config import (
  4. AgentConfig,
  5. AppConfig,
  6. LLMConfig,
  7. UndefinedString,
  8. finalize_config,
  9. get_llm_config_arg,
  10. load_from_env,
  11. load_from_toml,
  12. )
  13. @pytest.fixture
  14. def setup_env():
  15. # Create old-style and new-style TOML files
  16. with open('old_style_config.toml', 'w') as f:
  17. f.write('[default]\nLLM_MODEL="GPT-4"\n')
  18. with open('new_style_config.toml', 'w') as f:
  19. f.write('[app]\nLLM_MODEL="GPT-3"\n')
  20. yield
  21. # Cleanup TOML files after the test
  22. os.remove('old_style_config.toml')
  23. os.remove('new_style_config.toml')
  24. @pytest.fixture
  25. def temp_toml_file(tmp_path):
  26. # Fixture to create a temporary directory and TOML file for testing
  27. tmp_toml_file = os.path.join(tmp_path, 'config.toml')
  28. yield tmp_toml_file
  29. @pytest.fixture
  30. def default_config(monkeypatch):
  31. # Fixture to provide a default AppConfig instance
  32. AppConfig.reset()
  33. yield AppConfig()
  34. def test_compat_env_to_config(monkeypatch, setup_env):
  35. # Use `monkeypatch` to set environment variables for this specific test
  36. monkeypatch.setenv('WORKSPACE_BASE', '/repos/opendevin/workspace')
  37. monkeypatch.setenv('LLM_API_KEY', 'sk-proj-rgMV0...')
  38. monkeypatch.setenv('LLM_MODEL', 'gpt-4o')
  39. monkeypatch.setenv('AGENT_MEMORY_MAX_THREADS', '4')
  40. monkeypatch.setenv('AGENT_MEMORY_ENABLED', 'True')
  41. monkeypatch.setenv('DEFAULT_AGENT', 'CodeActAgent')
  42. monkeypatch.setenv('SANDBOX_TYPE', 'local')
  43. monkeypatch.setenv('SANDBOX_TIMEOUT', '10')
  44. config = AppConfig()
  45. load_from_env(config, os.environ)
  46. assert config.workspace_base == '/repos/opendevin/workspace'
  47. assert isinstance(config.get_llm_config(), LLMConfig)
  48. assert config.get_llm_config().api_key == 'sk-proj-rgMV0...'
  49. assert config.get_llm_config().model == 'gpt-4o'
  50. assert isinstance(config.get_agent_config(), AgentConfig)
  51. assert isinstance(config.get_agent_config().memory_max_threads, int)
  52. assert config.get_agent_config().memory_max_threads == 4
  53. assert config.get_agent_config().memory_enabled is True
  54. assert config.default_agent == 'CodeActAgent'
  55. assert config.sandbox.box_type == 'local'
  56. assert config.sandbox.timeout == 10
  57. def test_load_from_old_style_env(monkeypatch, default_config):
  58. # Test loading configuration from old-style environment variables using monkeypatch
  59. monkeypatch.setenv('LLM_API_KEY', 'test-api-key')
  60. monkeypatch.setenv('AGENT_MEMORY_ENABLED', 'True')
  61. monkeypatch.setenv('DEFAULT_AGENT', 'PlannerAgent')
  62. monkeypatch.setenv('WORKSPACE_BASE', '/opt/files/workspace')
  63. monkeypatch.setenv('SANDBOX_CONTAINER_IMAGE', 'custom_image')
  64. load_from_env(default_config, os.environ)
  65. assert default_config.get_llm_config().api_key == 'test-api-key'
  66. assert default_config.get_agent_config().memory_enabled is True
  67. assert default_config.default_agent == 'PlannerAgent'
  68. assert default_config.workspace_base == '/opt/files/workspace'
  69. assert (
  70. default_config.workspace_mount_path is UndefinedString.UNDEFINED
  71. ) # before finalize_config
  72. assert (
  73. default_config.workspace_mount_path_in_sandbox is not UndefinedString.UNDEFINED
  74. )
  75. assert default_config.sandbox.container_image == 'custom_image'
  76. def test_load_from_new_style_toml(default_config, temp_toml_file):
  77. # Test loading configuration from a new-style TOML file
  78. with open(temp_toml_file, 'w', encoding='utf-8') as toml_file:
  79. toml_file.write(
  80. """
  81. [llm]
  82. model = "test-model"
  83. api_key = "toml-api-key"
  84. [llm.cheap]
  85. model = "some-cheap-model"
  86. api_key = "cheap-model-api-key"
  87. [agent]
  88. memory_enabled = true
  89. [agent.BrowsingAgent]
  90. llm_config = "cheap"
  91. memory_enabled = false
  92. [sandbox]
  93. timeout = 1
  94. [core]
  95. workspace_base = "/opt/files2/workspace"
  96. default_agent = "TestAgent"
  97. sandbox_type = "local"
  98. """
  99. )
  100. load_from_toml(default_config, temp_toml_file)
  101. # default llm & agent configs
  102. assert default_config.default_agent == 'TestAgent'
  103. assert default_config.get_llm_config().model == 'test-model'
  104. assert default_config.get_llm_config().api_key == 'toml-api-key'
  105. assert default_config.get_agent_config().memory_enabled is True
  106. # undefined agent config inherits default ones
  107. assert (
  108. default_config.get_llm_config_from_agent('CodeActAgent')
  109. == default_config.get_llm_config()
  110. )
  111. assert default_config.get_agent_config('CodeActAgent').memory_enabled is True
  112. # defined agent config overrides default ones
  113. assert default_config.get_llm_config_from_agent(
  114. 'BrowsingAgent'
  115. ) == default_config.get_llm_config('cheap')
  116. assert (
  117. default_config.get_llm_config_from_agent('BrowsingAgent').model
  118. == 'some-cheap-model'
  119. )
  120. assert default_config.get_agent_config('BrowsingAgent').memory_enabled is False
  121. assert default_config.workspace_base == '/opt/files2/workspace'
  122. assert default_config.sandbox.box_type == 'local'
  123. assert default_config.sandbox.timeout == 1
  124. # default config doesn't have a field sandbox_type
  125. assert not hasattr(default_config, 'sandbox_type')
  126. # before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set
  127. assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
  128. assert (
  129. default_config.workspace_mount_path_in_sandbox is not UndefinedString.UNDEFINED
  130. )
  131. assert default_config.workspace_mount_path_in_sandbox == '/workspace'
  132. finalize_config(default_config)
  133. # after finalize_config, workspace_mount_path is set to the absolute path of workspace_base
  134. # if it was undefined
  135. assert default_config.workspace_mount_path == '/opt/files2/workspace'
  136. def test_compat_load_sandbox_from_toml(default_config, temp_toml_file):
  137. # test loading configuration from a new-style TOML file
  138. # uses a toml file with sandbox_vars instead of a sandbox section
  139. with open(temp_toml_file, 'w', encoding='utf-8') as toml_file:
  140. toml_file.write(
  141. """
  142. [llm]
  143. model = "test-model"
  144. [agent]
  145. memory_enabled = true
  146. [core]
  147. workspace_base = "/opt/files2/workspace"
  148. sandbox_type = "local"
  149. sandbox_timeout = 500
  150. sandbox_container_image = "node:14"
  151. sandbox_user_id = 1001
  152. default_agent = "TestAgent"
  153. """
  154. )
  155. load_from_toml(default_config, temp_toml_file)
  156. assert default_config.get_llm_config().model == 'test-model'
  157. assert default_config.get_llm_config_from_agent().model == 'test-model'
  158. assert default_config.default_agent == 'TestAgent'
  159. assert default_config.get_agent_config().memory_enabled is True
  160. assert default_config.workspace_base == '/opt/files2/workspace'
  161. assert default_config.sandbox.box_type == 'local'
  162. assert default_config.sandbox.timeout == 500
  163. assert default_config.sandbox.container_image == 'node:14'
  164. assert default_config.sandbox.user_id == 1001
  165. assert default_config.workspace_mount_path_in_sandbox == '/workspace'
  166. finalize_config(default_config)
  167. # app config doesn't have fields sandbox_*
  168. assert not hasattr(default_config, 'sandbox_type')
  169. assert not hasattr(default_config, 'sandbox_timeout')
  170. assert not hasattr(default_config, 'sandbox_container_image')
  171. assert not hasattr(default_config, 'sandbox_user_id')
  172. # after finalize_config, workspace_mount_path is set to the absolute path of workspace_base
  173. # if it was undefined
  174. assert default_config.workspace_mount_path == '/opt/files2/workspace'
  175. def test_env_overrides_compat_toml(monkeypatch, default_config, temp_toml_file):
  176. # test that environment variables override TOML values using monkeypatch
  177. # uses a toml file with sandbox_vars instead of a sandbox section
  178. with open(temp_toml_file, 'w', encoding='utf-8') as toml_file:
  179. toml_file.write("""
  180. [llm]
  181. model = "test-model"
  182. api_key = "toml-api-key"
  183. [core]
  184. workspace_base = "/opt/files3/workspace"
  185. sandbox_type = "local"
  186. disable_color = true
  187. sandbox_timeout = 500
  188. sandbox_user_id = 1001
  189. """)
  190. monkeypatch.setenv('LLM_API_KEY', 'env-api-key')
  191. monkeypatch.setenv('WORKSPACE_BASE', 'UNDEFINED')
  192. monkeypatch.setenv('SANDBOX_TYPE', 'e2b')
  193. monkeypatch.setenv('SANDBOX_TIMEOUT', '1000')
  194. monkeypatch.setenv('SANDBOX_USER_ID', '1002')
  195. load_from_toml(default_config, temp_toml_file)
  196. # before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set
  197. assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
  198. load_from_env(default_config, os.environ)
  199. assert os.environ.get('LLM_MODEL') is None
  200. assert default_config.get_llm_config().model == 'test-model'
  201. assert default_config.get_llm_config('llm').model == 'test-model'
  202. assert default_config.get_llm_config_from_agent().model == 'test-model'
  203. assert default_config.get_llm_config().api_key == 'env-api-key'
  204. # after we set workspace_base to 'UNDEFINED' in the environment,
  205. # workspace_base should be set to that
  206. # workspace_mount path is still UndefinedString.UNDEFINED
  207. assert default_config.workspace_base is not UndefinedString.UNDEFINED
  208. assert default_config.workspace_base == 'UNDEFINED'
  209. assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
  210. assert default_config.workspace_mount_path == 'UNDEFINED'
  211. assert default_config.sandbox.box_type == 'e2b'
  212. assert default_config.disable_color is True
  213. assert default_config.sandbox.timeout == 1000
  214. assert default_config.sandbox.user_id == 1002
  215. finalize_config(default_config)
  216. # after finalize_config, workspace_mount_path is set to absolute path of workspace_base if it was undefined
  217. assert default_config.workspace_mount_path == os.getcwd() + '/UNDEFINED'
  218. def test_env_overrides_sandbox_toml(monkeypatch, default_config, temp_toml_file):
  219. # test that environment variables override TOML values using monkeypatch
  220. # uses a toml file with a sandbox section
  221. with open(temp_toml_file, 'w', encoding='utf-8') as toml_file:
  222. toml_file.write("""
  223. [llm]
  224. model = "test-model"
  225. api_key = "toml-api-key"
  226. [core]
  227. workspace_base = "/opt/files3/workspace"
  228. [sandbox]
  229. box_type = "e2b"
  230. timeout = 500
  231. user_id = 1001
  232. """)
  233. monkeypatch.setenv('LLM_API_KEY', 'env-api-key')
  234. monkeypatch.setenv('WORKSPACE_BASE', 'UNDEFINED')
  235. monkeypatch.setenv('SANDBOX_TYPE', 'local')
  236. monkeypatch.setenv('SANDBOX_TIMEOUT', '1000')
  237. monkeypatch.setenv('SANDBOX_USER_ID', '1002')
  238. load_from_toml(default_config, temp_toml_file)
  239. # before finalize_config, workspace_mount_path is UndefinedString.UNDEFINED if it was not set
  240. assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
  241. # before load_from_env, values are set to the values from the toml file
  242. assert default_config.get_llm_config().api_key == 'toml-api-key'
  243. assert default_config.sandbox.box_type == 'e2b'
  244. assert default_config.sandbox.timeout == 500
  245. assert default_config.sandbox.user_id == 1001
  246. load_from_env(default_config, os.environ)
  247. # values from env override values from toml
  248. assert os.environ.get('LLM_MODEL') is None
  249. assert default_config.get_llm_config().model == 'test-model'
  250. assert default_config.get_llm_config().api_key == 'env-api-key'
  251. assert default_config.sandbox.box_type == 'local'
  252. assert default_config.sandbox.timeout == 1000
  253. assert default_config.sandbox.user_id == 1002
  254. finalize_config(default_config)
  255. # after finalize_config, workspace_mount_path is set to absolute path of workspace_base if it was undefined
  256. assert default_config.workspace_mount_path == os.getcwd() + '/UNDEFINED'
  257. def test_sandbox_config_from_toml(default_config, temp_toml_file):
  258. # Test loading configuration from a new-style TOML file
  259. with open(temp_toml_file, 'w', encoding='utf-8') as toml_file:
  260. toml_file.write(
  261. """
  262. [core]
  263. workspace_base = "/opt/files/workspace"
  264. [llm]
  265. model = "test-model"
  266. [sandbox]
  267. box_type = "local"
  268. timeout = 1
  269. container_image = "custom_image"
  270. user_id = 1001
  271. """
  272. )
  273. load_from_toml(default_config, temp_toml_file)
  274. load_from_env(default_config, os.environ)
  275. finalize_config(default_config)
  276. assert default_config.get_llm_config().model == 'test-model'
  277. assert default_config.sandbox.box_type == 'local'
  278. assert default_config.sandbox.timeout == 1
  279. assert default_config.sandbox.container_image == 'custom_image'
  280. assert default_config.sandbox.user_id == 1001
  281. def test_defaults_dict_after_updates(default_config):
  282. # Test that `defaults_dict` retains initial values after updates.
  283. initial_defaults = default_config.defaults_dict
  284. assert (
  285. initial_defaults['workspace_mount_path']['default'] is UndefinedString.UNDEFINED
  286. )
  287. assert initial_defaults['default_agent']['default'] == 'CodeActAgent'
  288. updated_config = AppConfig()
  289. updated_config.get_llm_config().api_key = 'updated-api-key'
  290. updated_config.get_llm_config('llm').api_key = 'updated-api-key'
  291. updated_config.get_llm_config_from_agent('agent').api_key = 'updated-api-key'
  292. updated_config.get_llm_config_from_agent(
  293. 'MonologueAgent'
  294. ).api_key = 'updated-api-key'
  295. updated_config.default_agent = 'MonologueAgent'
  296. defaults_after_updates = updated_config.defaults_dict
  297. assert defaults_after_updates['default_agent']['default'] == 'CodeActAgent'
  298. assert (
  299. defaults_after_updates['workspace_mount_path']['default']
  300. is UndefinedString.UNDEFINED
  301. )
  302. assert defaults_after_updates['sandbox']['box_type']['default'] == 'ssh'
  303. assert defaults_after_updates['sandbox']['timeout']['default'] == 120
  304. assert (
  305. defaults_after_updates['sandbox']['container_image']['default']
  306. == 'ghcr.io/opendevin/sandbox:main'
  307. )
  308. assert defaults_after_updates == initial_defaults
  309. def test_invalid_toml_format(monkeypatch, temp_toml_file, default_config):
  310. # Invalid TOML format doesn't break the configuration
  311. monkeypatch.setenv('LLM_MODEL', 'gpt-5-turbo-1106')
  312. monkeypatch.setenv('WORKSPACE_MOUNT_PATH', '/home/user/project')
  313. monkeypatch.delenv('LLM_API_KEY', raising=False)
  314. with open(temp_toml_file, 'w', encoding='utf-8') as toml_file:
  315. toml_file.write('INVALID TOML CONTENT')
  316. load_from_toml(default_config)
  317. load_from_env(default_config, os.environ)
  318. default_config.ssh_password = None # prevent leak
  319. default_config.jwt_secret = None # prevent leak
  320. for llm in default_config.llms.values():
  321. llm.api_key = None # prevent leak
  322. assert default_config.get_llm_config().model == 'gpt-5-turbo-1106'
  323. assert default_config.get_llm_config().custom_llm_provider is None
  324. assert default_config.workspace_mount_path == '/home/user/project'
  325. def test_finalize_config(default_config):
  326. # Test finalize config
  327. assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
  328. default_config.sandbox.box_type = 'local'
  329. finalize_config(default_config)
  330. assert (
  331. default_config.workspace_mount_path_in_sandbox
  332. == default_config.workspace_mount_path
  333. )
  334. assert default_config.workspace_mount_path == os.path.abspath(
  335. default_config.workspace_base
  336. )
  337. # tests for workspace, mount path, path in sandbox, cache dir
  338. def test_workspace_mount_path_default(default_config):
  339. assert default_config.workspace_mount_path is UndefinedString.UNDEFINED
  340. finalize_config(default_config)
  341. assert default_config.workspace_mount_path == os.path.abspath(
  342. default_config.workspace_base
  343. )
  344. def test_workspace_mount_path_in_sandbox_local(default_config):
  345. assert default_config.workspace_mount_path_in_sandbox == '/workspace'
  346. default_config.sandbox.box_type = 'local'
  347. finalize_config(default_config)
  348. assert (
  349. default_config.workspace_mount_path_in_sandbox
  350. == default_config.workspace_mount_path
  351. )
  352. def test_workspace_mount_rewrite(default_config, monkeypatch):
  353. default_config.workspace_base = '/home/user/project'
  354. default_config.workspace_mount_rewrite = '/home/user:/sandbox'
  355. monkeypatch.setattr('os.getcwd', lambda: '/current/working/directory')
  356. finalize_config(default_config)
  357. assert default_config.workspace_mount_path == '/sandbox/project'
  358. def test_embedding_base_url_default(default_config):
  359. default_config.get_llm_config().base_url = 'https://api.exampleapi.com'
  360. finalize_config(default_config)
  361. assert (
  362. default_config.get_llm_config().embedding_base_url
  363. == 'https://api.exampleapi.com'
  364. )
  365. def test_cache_dir_creation(default_config, tmpdir):
  366. default_config.cache_dir = str(tmpdir.join('test_cache'))
  367. finalize_config(default_config)
  368. assert os.path.exists(default_config.cache_dir)
  369. def test_api_keys_repr_str():
  370. # Test LLMConfig
  371. llm_config = LLMConfig(
  372. api_key='my_api_key',
  373. aws_access_key_id='my_access_key',
  374. aws_secret_access_key='my_secret_key',
  375. )
  376. assert "api_key='******'" in repr(llm_config)
  377. assert "aws_access_key_id='******'" in repr(llm_config)
  378. assert "aws_secret_access_key='******'" in repr(llm_config)
  379. assert "api_key='******'" in str(llm_config)
  380. assert "aws_access_key_id='******'" in str(llm_config)
  381. assert "aws_secret_access_key='******'" in str(llm_config)
  382. # Check that no other attrs in LLMConfig have 'key' or 'token' in their name
  383. # This will fail when new attrs are added, and attract attention
  384. known_key_token_attrs_llm = [
  385. 'api_key',
  386. 'aws_access_key_id',
  387. 'aws_secret_access_key',
  388. 'input_cost_per_token',
  389. 'output_cost_per_token',
  390. ]
  391. for attr_name in dir(LLMConfig):
  392. if (
  393. not attr_name.startswith('__')
  394. and attr_name not in known_key_token_attrs_llm
  395. ):
  396. assert (
  397. 'key' not in attr_name.lower()
  398. ), f"Unexpected attribute '{attr_name}' contains 'key' in LLMConfig"
  399. assert (
  400. 'token' not in attr_name.lower() or 'tokens' in attr_name.lower()
  401. ), f"Unexpected attribute '{attr_name}' contains 'token' in LLMConfig"
  402. # Test AgentConfig
  403. # No attrs in AgentConfig have 'key' or 'token' in their name
  404. agent_config = AgentConfig(memory_enabled=True, memory_max_threads=4)
  405. for attr_name in dir(AgentConfig):
  406. if not attr_name.startswith('__'):
  407. assert (
  408. 'key' not in attr_name.lower()
  409. ), f"Unexpected attribute '{attr_name}' contains 'key' in AgentConfig"
  410. assert (
  411. 'token' not in attr_name.lower() or 'tokens' in attr_name.lower()
  412. ), f"Unexpected attribute '{attr_name}' contains 'token' in AgentConfig"
  413. # Test AppConfig
  414. app_config = AppConfig(
  415. llms={'llm': llm_config},
  416. agents={'agent': agent_config},
  417. e2b_api_key='my_e2b_api_key',
  418. jwt_secret='my_jwt_secret',
  419. ssh_password='my_ssh_password',
  420. )
  421. assert "e2b_api_key='******'" in repr(app_config)
  422. assert "e2b_api_key='******'" in str(app_config)
  423. assert "jwt_secret='******'" in repr(app_config)
  424. assert "jwt_secret='******'" in str(app_config)
  425. assert "ssh_password='******'" in repr(app_config)
  426. assert "ssh_password='******'" in str(app_config)
  427. # Check that no other attrs in AppConfig have 'key' or 'token' in their name
  428. # This will fail when new attrs are added, and attract attention
  429. known_key_token_attrs_app = ['e2b_api_key']
  430. for attr_name in dir(AppConfig):
  431. if (
  432. not attr_name.startswith('__')
  433. and attr_name not in known_key_token_attrs_app
  434. ):
  435. assert (
  436. 'key' not in attr_name.lower()
  437. ), f"Unexpected attribute '{attr_name}' contains 'key' in AppConfig"
  438. assert (
  439. 'token' not in attr_name.lower() or 'tokens' in attr_name.lower()
  440. ), f"Unexpected attribute '{attr_name}' contains 'token' in AppConfig"
  441. def test_max_iterations_and_max_budget_per_task_from_toml(temp_toml_file):
  442. temp_toml = """
  443. [core]
  444. max_iterations = 100
  445. max_budget_per_task = 4.0
  446. """
  447. config = AppConfig()
  448. with open(temp_toml_file, 'w') as f:
  449. f.write(temp_toml)
  450. load_from_toml(config, temp_toml_file)
  451. assert config.max_iterations == 100
  452. assert config.max_budget_per_task == 4.0
  453. def test_get_llm_config_arg(temp_toml_file):
  454. temp_toml = """
  455. [core]
  456. max_iterations = 100
  457. max_budget_per_task = 4.0
  458. [llm.gpt3]
  459. model="gpt-3.5-turbo"
  460. api_key="redacted"
  461. embedding_model="openai"
  462. [llm.gpt4o]
  463. model="gpt-4o"
  464. api_key="redacted"
  465. embedding_model="openai"
  466. """
  467. with open(temp_toml_file, 'w') as f:
  468. f.write(temp_toml)
  469. llm_config = get_llm_config_arg('gpt3', temp_toml_file)
  470. assert llm_config.model == 'gpt-3.5-turbo'
  471. assert llm_config.embedding_model == 'openai'