deepeval 3.4.7__py3-none-any.whl → 3.4.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. deepeval/__init__.py +8 -7
  2. deepeval/_version.py +1 -1
  3. deepeval/cli/dotenv_handler.py +71 -0
  4. deepeval/cli/main.py +1021 -280
  5. deepeval/cli/utils.py +116 -2
  6. deepeval/confident/api.py +29 -14
  7. deepeval/config/__init__.py +0 -0
  8. deepeval/config/settings.py +565 -0
  9. deepeval/config/settings_manager.py +133 -0
  10. deepeval/config/utils.py +86 -0
  11. deepeval/dataset/__init__.py +1 -0
  12. deepeval/dataset/dataset.py +70 -10
  13. deepeval/dataset/test_run_tracer.py +82 -0
  14. deepeval/dataset/utils.py +23 -0
  15. deepeval/key_handler.py +64 -2
  16. deepeval/metrics/__init__.py +4 -1
  17. deepeval/metrics/answer_relevancy/template.py +7 -2
  18. deepeval/metrics/conversational_dag/__init__.py +7 -0
  19. deepeval/metrics/conversational_dag/conversational_dag.py +139 -0
  20. deepeval/metrics/conversational_dag/nodes.py +931 -0
  21. deepeval/metrics/conversational_dag/templates.py +117 -0
  22. deepeval/metrics/dag/dag.py +13 -4
  23. deepeval/metrics/dag/graph.py +47 -15
  24. deepeval/metrics/dag/utils.py +103 -38
  25. deepeval/metrics/faithfulness/template.py +11 -8
  26. deepeval/metrics/multimodal_metrics/multimodal_answer_relevancy/template.py +6 -4
  27. deepeval/metrics/multimodal_metrics/multimodal_faithfulness/template.py +6 -4
  28. deepeval/metrics/tool_correctness/tool_correctness.py +7 -3
  29. deepeval/models/llms/amazon_bedrock_model.py +24 -3
  30. deepeval/models/llms/openai_model.py +37 -41
  31. deepeval/models/retry_policy.py +280 -0
  32. deepeval/openai_agents/agent.py +4 -2
  33. deepeval/synthesizer/chunking/doc_chunker.py +87 -51
  34. deepeval/test_run/api.py +1 -0
  35. deepeval/tracing/otel/exporter.py +20 -8
  36. deepeval/tracing/otel/utils.py +57 -0
  37. deepeval/tracing/tracing.py +37 -16
  38. deepeval/tracing/utils.py +98 -1
  39. deepeval/utils.py +111 -70
  40. {deepeval-3.4.7.dist-info → deepeval-3.4.9.dist-info}/METADATA +3 -1
  41. {deepeval-3.4.7.dist-info → deepeval-3.4.9.dist-info}/RECORD +44 -34
  42. deepeval/env.py +0 -35
  43. {deepeval-3.4.7.dist-info → deepeval-3.4.9.dist-info}/LICENSE.md +0 -0
  44. {deepeval-3.4.7.dist-info → deepeval-3.4.9.dist-info}/WHEEL +0 -0
  45. {deepeval-3.4.7.dist-info → deepeval-3.4.9.dist-info}/entry_points.txt +0 -0
deepeval/cli/main.py CHANGED
@@ -1,6 +1,24 @@
1
+ """
2
+ DeepEval CLI: Model Provider Configuration Commands
3
+
4
+ General behavior for all `set-*` / `unset-*` commands:
5
+
6
+ - Non-secret settings (model name, endpoint, deployment, toggles) are always
7
+ persisted in the hidden `.deepeval/.deepeval` JSON store.
8
+ - Secrets (API keys) are **never** written to the JSON store.
9
+ - If `--save=dotenv[:path]` is passed, both secrets and non-secrets are
10
+ written to the specified dotenv file (default: `.env.local`).
11
+ Dotenv files should be git-ignored.
12
+ - If `--save` is not passed, only the JSON store is updated.
13
+ - When unsetting a provider, only that provider’s keys are removed.
14
+ If another provider’s credentials remain (e.g. `OPENAI_API_KEY`), it
15
+ may still be selected as the default.
16
+ """
17
+
1
18
  import os
2
19
  from typing import Optional
3
20
  from rich import print
21
+ from rich.markup import escape
4
22
  import webbrowser
5
23
  import threading
6
24
  import random
@@ -8,6 +26,7 @@ import string
8
26
  import socket
9
27
  import typer
10
28
  from enum import Enum
29
+ from pydantic import SecretStr
11
30
  from deepeval.key_handler import (
12
31
  KEY_FILE_HANDLER,
13
32
  KeyValues,
@@ -17,6 +36,7 @@ from deepeval.key_handler import (
17
36
  from deepeval.telemetry import capture_login_event, capture_view_event
18
37
  from deepeval.cli.test import app as test_app
19
38
  from deepeval.cli.server import start_server
39
+ from deepeval.config.settings import get_settings
20
40
  from deepeval.utils import delete_file_if_exists, open_browser
21
41
  from deepeval.test_run.test_run import (
22
42
  LATEST_TEST_RUN_FILE_PATH,
@@ -26,13 +46,16 @@ from deepeval.cli.utils import (
26
46
  render_login_message,
27
47
  upload_and_open_link,
28
48
  PROD,
29
- clear_evaluation_model_keys,
30
- clear_embedding_model_keys,
49
+ resolve_save_target,
50
+ save_environ_to_store,
51
+ unset_environ_in_store,
52
+ switch_model_provider,
31
53
  )
32
54
  from deepeval.confident.api import (
33
55
  get_confident_api_key,
34
56
  is_confident,
35
57
  set_confident_api_key,
58
+ CONFIDENT_API_KEY_ENV_VAR,
36
59
  )
37
60
 
38
61
  app = typer.Typer(name="deepeval")
@@ -55,16 +78,58 @@ def find_available_port():
55
78
  return s.getsockname()[1]
56
79
 
57
80
 
81
+ def is_openai_configured() -> bool:
82
+ s = get_settings()
83
+ v = s.OPENAI_API_KEY
84
+ if isinstance(v, SecretStr):
85
+ try:
86
+ if v.get_secret_value().strip():
87
+ return True
88
+ except Exception:
89
+ pass
90
+ elif v and str(v).strip():
91
+ return True
92
+ env = os.getenv("OPENAI_API_KEY")
93
+ return bool(env and env.strip())
94
+
95
+
58
96
  @app.command(name="set-confident-region")
59
97
  def set_confident_region_command(
60
98
  region: Regions = typer.Argument(
61
99
  ..., help="The data region to use (US or EU)"
62
- )
100
+ ),
101
+ save: Optional[str] = typer.Option(
102
+ None,
103
+ "--save",
104
+ help="Persist CLI parameters as environment variables in a dotenv file. "
105
+ "Usage: --save=dotenv[:path] (default: .env.local)",
106
+ ),
63
107
  ):
64
108
  """Set the Confident AI data region."""
65
109
  # Add flag emojis based on region
66
110
  flag = "🇺🇸" if region == Regions.US else "🇪🇺"
67
- KEY_FILE_HANDLER.write_key(KeyValues.CONFIDENT_REGION, region.value)
111
+
112
+ setting = get_settings()
113
+ with settings.edit(save=save) as edit_ctx:
114
+ settings.CONFIDENT_REGION = region.value
115
+
116
+ handled, path, _ = edit_ctx.result
117
+
118
+ if not handled and save is not None:
119
+ # invalid --save format (unsupported)
120
+ print("Unsupported --save option. Use --save=dotenv[:path].")
121
+ elif path:
122
+ # persisted to a file
123
+ print(
124
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
125
+ )
126
+ else:
127
+ # updated in-memory & process env only
128
+ print(
129
+ "Settings updated for this session. To persist, use --save=dotenv[:path] "
130
+ "(default .env.local) or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
131
+ )
132
+
68
133
  print(
69
134
  f":raising_hands: Congratulations! You're now using the {flag} {region.value} data region for Confident AI."
70
135
  )
@@ -80,13 +145,20 @@ def login(
80
145
  None,
81
146
  "--confident-api-key",
82
147
  "-c",
83
- help="Optional confident API key to bypass login.",
148
+ help="Confident API key (non-interactive). If omitted, you'll be prompted to enter one. In all cases the key is saved to a dotenv file (default: .env.local) unless overridden with --save.",
149
+ ),
150
+ save: Optional[str] = typer.Option(
151
+ None,
152
+ "--save",
153
+ help="Where to persist settings. Format: dotenv[:path]. Defaults to .env.local. If omitted, login still writes to .env.local.",
84
154
  ),
85
155
  ):
86
156
  with capture_login_event() as span:
157
+ completed = False
87
158
  try:
159
+ # Resolve the key from CLI flag or interactive flow
88
160
  if confident_api_key:
89
- api_key = confident_api_key
161
+ key = confident_api_key.strip()
90
162
  else:
91
163
  render_login_message()
92
164
 
@@ -104,35 +176,93 @@ def login(
104
176
  login_url = f"{PROD}/pair?code={pairing_code}&port={port}"
105
177
  webbrowser.open(login_url)
106
178
  print(
107
- f"(open this link if your browser did not opend: [link={PROD}]{PROD}[/link])"
179
+ f"(open this link if your browser did not open: [link={PROD}]{PROD}[/link])"
108
180
  )
181
+
182
+ # Manual fallback if still empty
109
183
  if api_key == "":
110
184
  while True:
111
- api_key = input(f"🔐 Enter your API Key: ").strip()
185
+ api_key = input("🔐 Enter your API Key: ").strip()
112
186
  if api_key:
113
187
  break
114
188
  else:
115
189
  print(
116
190
  "❌ API Key cannot be empty. Please try again.\n"
117
191
  )
118
-
119
- set_confident_api_key(api_key)
120
- span.set_attribute("completed", True)
121
-
192
+ key = api_key.strip()
193
+
194
+ settings = get_settings()
195
+ save = save or settings.DEEPEVAL_DEFAULT_SAVE or "dotenv:.env.local"
196
+ with settings.edit(save=save) as edit_ctx:
197
+ settings.API_KEY = key
198
+ settings.CONFIDENT_API_KEY = key
199
+
200
+ handled, path, updated = edit_ctx.result
201
+
202
+ if updated:
203
+ if not handled and save is not None:
204
+ # invalid --save format (unsupported)
205
+ print(
206
+ "Unsupported --save option. Use --save=dotenv[:path]."
207
+ )
208
+ elif path:
209
+ # persisted to a file
210
+ print(
211
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
212
+ )
213
+
214
+ completed = True
122
215
  print(
123
- "\n🎉🥳 Congratulations! You've successfully logged in! :raising_hands: "
216
+ "\n🎉🥳 Congratulations! You've successfully logged in! :raising_hands:"
124
217
  )
125
218
  print(
126
- "You're now using DeepEval with [rgb(106,0,255)]Confident AI[/rgb(106,0,255)]. Follow our quickstart tutorial here: [bold][link=https://www.confident-ai.com/docs/llm-evaluation/quickstart]https://www.confident-ai.com/docs/llm-evaluation/quickstart[/link][/bold]"
219
+ "You're now using DeepEval with [rgb(106,0,255)]Confident AI[/rgb(106,0,255)]. "
220
+ "Follow our quickstart tutorial here: "
221
+ "[bold][link=https://www.confident-ai.com/docs/llm-evaluation/quickstart]"
222
+ "https://www.confident-ai.com/docs/llm-evaluation/quickstart[/link][/bold]"
127
223
  )
128
- except:
129
- span.set_attribute("completed", False)
224
+ except Exception as e:
225
+ completed = False
226
+ print(f"Login failed: {e}")
227
+ finally:
228
+ if getattr(span, "set_attribute", None):
229
+ span.set_attribute("completed", completed)
130
230
 
131
231
 
132
232
  @app.command()
133
- def logout():
134
- set_confident_api_key(None)
233
+ def logout(
234
+ save: Optional[str] = typer.Option(
235
+ None,
236
+ "--save",
237
+ help="Where to remove the saved key from. Use format dotenv[:path]. If omitted, uses DEEPEVAL_DEFAULT_SAVE or .env.local. The JSON keystore is always cleared.",
238
+ )
239
+ ):
240
+ """
241
+ Log out of Confident AI.
242
+
243
+ Behavior:
244
+ - Always clears the Confident API key from the JSON keystore and process env.
245
+ - Also removes credentials from a dotenv file; defaults to DEEPEVAL_DEFAULT_SAVE if set, otherwise.env.local.
246
+ Override the target with --save=dotenv[:path].
247
+ """
248
+ settings = get_settings()
249
+ save = save or settings.DEEPEVAL_DEFAULT_SAVE or "dotenv:.env.local"
250
+ with settings.edit(save=save) as edit_ctx:
251
+ settings.API_KEY = None
252
+ settings.CONFIDENT_API_KEY = None
253
+
254
+ handled, path, updated = edit_ctx.result
255
+
256
+ if updated:
257
+ if not handled and save is not None:
258
+ # invalid --save format (unsupported)
259
+ print("Unsupported --save option. Use --save=dotenv[:path].")
260
+ elif path:
261
+ # persisted to a file
262
+ print(f"Removed Confident AI key(s) from {path}.")
263
+
135
264
  delete_file_if_exists(LATEST_TEST_RUN_FILE_PATH)
265
+
136
266
  print("\n🎉🥳 You've successfully logged out! :raising_hands: ")
137
267
 
138
268
 
@@ -153,8 +283,22 @@ def view():
153
283
 
154
284
 
155
285
  @app.command(name="enable-grpc-logging")
156
- def enable_grpc_logging():
157
- os.environ["DEEPEVAL_GRPC_LOGGING"] = "1"
286
+ def enable_grpc_logging(save: Optional[str] = None):
287
+ """
288
+ Enable verbose gRPC logging for the current process.
289
+ Pass --save=dotenv[:path] to persist it (optional).
290
+ """
291
+ settings = get_settings()
292
+ with settings.edit(save=save) as edit_ctx:
293
+ settings.DEEPEVAL_GRPC_LOGGING = True
294
+
295
+ handled, path, _ = edit_ctx.result
296
+
297
+ if not handled and save is not None:
298
+ # invalid --save format (unsupported)
299
+ print("Unsupported --save option. Use --save=dotenv[:path].")
300
+ else:
301
+ print("gRPC logging enabled.")
158
302
 
159
303
 
160
304
  #############################################
@@ -181,37 +325,126 @@ def set_openai_env(
181
325
  "REQUIRED if you use a custom/unsupported model."
182
326
  ),
183
327
  ),
328
+ save: Optional[str] = typer.Option(
329
+ None,
330
+ "--save",
331
+ help="Persist CLI parameters as environment variables in a dotenv file. "
332
+ "Usage: --save=dotenv[:path] (default: .env.local)",
333
+ ),
184
334
  ):
185
- """Configure OpenAI as the active model.
186
-
187
- Notes:
188
- - If `model` is a known OpenAI model, costs can be omitted (built-in pricing will be used).
189
- - If `model` is custom/unsupported, you must pass both --cost_per_input_token and --cost_per_output_token.
190
335
  """
336
+ Configure OpenAI as the active LLM provider.
337
+
338
+ What this does:
339
+ - Sets the active provider flag to `USE_OPENAI_MODEL`.
340
+ - Persists the selected model name and any cost overrides in the JSON store.
341
+ - secrets are never written to `.deepeval/.deepeval` (JSON).
342
+
343
+ Pricing rules:
344
+ - If `model` is a known OpenAI model, you may omit costs (built‑in pricing is used).
345
+ - If `model` is custom/unsupported, you must provide both
346
+ `--cost_per_input_token` and `--cost_per_output_token`.
347
+
348
+ Secrets & saving:
349
+ - Set your `OPENAI_API_KEY` via environment or a dotenv file.
350
+ - Pass `--save=dotenv[:path]` to write configuration to a dotenv file
351
+ (default: `.env.local`). This command does not set or persist OPENAI_API_KEY. Set it
352
+ via your environment or a dotenv file (e.g., add OPENAI_API_KEY=... to .env.local)
353
+ before running this command, or manage it with whatever command you use for secrets.
354
+
355
+ Args:
356
+ model: OpenAI model name, such as `gpt-4o-mini`.
357
+ cost_per_input_token: USD per input token (optional for known models).
358
+ cost_per_output_token: USD per output token (optional for known models).
359
+ save: Persist config (and supported secrets) to a dotenv file; format `dotenv[:path]`.
360
+
361
+ Example:
362
+ deepeval set-openai \\
363
+ --model gpt-4o-mini \\
364
+ --cost_per_input_token 0.0005 \\
365
+ --cost_per_output_token 0.0015 \\
366
+ --save dotenv:.env.local
367
+ """
368
+ settings = get_settings()
369
+ with settings.edit(save=save) as edit_ctx:
370
+ edit_ctx.switch_model_provider(ModelKeyValues.USE_OPENAI_MODEL)
371
+ settings.OPENAI_MODEL_NAME = model
372
+ if cost_per_input_token is not None:
373
+ settings.OPENAI_COST_PER_INPUT_TOKEN = cost_per_input_token
374
+ if cost_per_output_token is not None:
375
+ settings.OPENAI_COST_PER_OUTPUT_TOKEN = cost_per_output_token
376
+
377
+ handled, path, _ = edit_ctx.result
378
+
379
+ if not handled and save is not None:
380
+ # invalid --save format (unsupported)
381
+ print("Unsupported --save option. Use --save=dotenv[:path].")
382
+ elif path:
383
+ # persisted to a file
384
+ print(
385
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
386
+ )
387
+ else:
388
+ # updated in-memory & process env only
389
+ print(
390
+ "Tip: persist these settings to a dotenv file with --save=dotenv[:path] (default .env.local) "
391
+ "or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
392
+ )
191
393
 
192
- clear_evaluation_model_keys()
193
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_OPENAI_MODEL, "YES")
194
- KEY_FILE_HANDLER.write_key(ModelKeyValues.OPENAI_MODEL_NAME, model)
195
- KEY_FILE_HANDLER.write_key(
196
- ModelKeyValues.OPENAI_COST_PER_INPUT_TOKEN, str(cost_per_input_token)
197
- )
198
- KEY_FILE_HANDLER.write_key(
199
- ModelKeyValues.OPENAI_COST_PER_OUTPUT_TOKEN, str(cost_per_output_token)
200
- )
201
394
  print(
202
- f":raising_hands: Congratulations! You're now using OpenAI's `{model}` for all evals that require an LLM."
395
+ f":raising_hands: Congratulations! You're now using OpenAI's `{escape(model)}` for all evals that require an LLM."
203
396
  )
204
397
 
205
398
 
206
399
  @app.command(name="unset-openai")
207
- def unset_openai_env():
208
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.USE_OPENAI_MODEL)
209
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.OPENAI_MODEL_NAME)
210
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.OPENAI_COST_PER_INPUT_TOKEN)
211
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.OPENAI_COST_PER_OUTPUT_TOKEN)
212
- print(
213
- ":raising_hands: Congratulations! You're now using default OpenAI settings on DeepEval for all evals that require an LLM."
214
- )
400
+ def unset_openai_env(
401
+ save: Optional[str] = typer.Option(
402
+ None,
403
+ "--save",
404
+ help="Remove only the OpenAI related environment variables from a dotenv file. "
405
+ "Usage: --save=dotenv[:path] (default: .env.local)",
406
+ ),
407
+ ):
408
+ """
409
+ Unset OpenAI as the active provider.
410
+
411
+ Behavior:
412
+ - Removes OpenAI keys (model, costs, toggle) from the JSON store.
413
+ - If `--save` is provided, removes those keys from the specified dotenv file.
414
+ - After unsetting, if `OPENAI_API_KEY` is still set in the environment,
415
+ OpenAI may still be usable by default. Otherwise, no active provider is configured.
416
+
417
+ Args:
418
+ --save: Remove OpenAI keys from the given dotenv file as well.
419
+
420
+ Example:
421
+ deepeval unset-openai --save dotenv:.env.local
422
+ """
423
+
424
+ settings = get_settings()
425
+ with settings.edit(save=save) as edit_ctx:
426
+ settings.OPENAI_MODEL_NAME = None
427
+ settings.OPENAI_COST_PER_INPUT_TOKEN = None
428
+ settings.OPENAI_COST_PER_OUTPUT_TOKEN = None
429
+ settings.USE_OPENAI_MODEL = None
430
+
431
+ handled, path, _ = edit_ctx.result
432
+
433
+ if not handled and save is not None:
434
+ # invalid --save format (unsupported)
435
+ print("Unsupported --save option. Use --save=dotenv[:path].")
436
+ elif path:
437
+ # persisted to a file
438
+ print(f"Removed OpenAI environment variables from {path}.")
439
+
440
+ if is_openai_configured():
441
+ print(
442
+ ":raised_hands: OpenAI will still be used by default because OPENAI_API_KEY is set."
443
+ )
444
+ else:
445
+ print(
446
+ "OpenAI has been unset. No active provider is configured. Set one with the CLI, or add credentials to .env[.local]."
447
+ )
215
448
 
216
449
 
217
450
  #############################################
@@ -222,7 +455,9 @@ def unset_openai_env():
222
455
  @app.command(name="set-azure-openai")
223
456
  def set_azure_openai_env(
224
457
  azure_openai_api_key: str = typer.Option(
225
- ..., "--openai-api-key", help="Azure OpenAI API key"
458
+ ...,
459
+ "--openai-api-key",
460
+ help="Azure OpenAI API key",
226
461
  ),
227
462
  azure_openai_endpoint: str = typer.Option(
228
463
  ..., "--openai-endpoint", help="Azure OpenAI endpoint"
@@ -239,34 +474,43 @@ def set_azure_openai_env(
239
474
  azure_model_version: Optional[str] = typer.Option(
240
475
  None, "--model-version", help="Azure model version (optional)"
241
476
  ),
477
+ save: Optional[str] = typer.Option(
478
+ None,
479
+ "--save",
480
+ help="Persist CLI parameters as environment variables in a dotenv file. "
481
+ "Usage: --save=dotenv[:path] (default: .env.local)",
482
+ ),
242
483
  ):
243
- clear_evaluation_model_keys()
244
- KEY_FILE_HANDLER.write_key(
245
- ModelKeyValues.AZURE_OPENAI_API_KEY, azure_openai_api_key
246
- )
247
- KEY_FILE_HANDLER.write_key(
248
- ModelKeyValues.AZURE_MODEL_NAME, openai_model_name
249
- )
250
- KEY_FILE_HANDLER.write_key(
251
- ModelKeyValues.AZURE_OPENAI_ENDPOINT, azure_openai_endpoint
252
- )
253
- KEY_FILE_HANDLER.write_key(
254
- ModelKeyValues.OPENAI_API_VERSION, openai_api_version
255
- )
256
- KEY_FILE_HANDLER.write_key(
257
- ModelKeyValues.AZURE_DEPLOYMENT_NAME, azure_deployment_name
258
- )
259
-
260
- if azure_model_version is not None:
261
- KEY_FILE_HANDLER.write_key(
262
- ModelKeyValues.AZURE_MODEL_VERSION, azure_model_version
484
+ settings = get_settings()
485
+ with settings.edit(save=save) as edit_ctx:
486
+ edit_ctx.switch_model_provider(ModelKeyValues.USE_AZURE_OPENAI)
487
+ settings.AZURE_OPENAI_API_KEY = azure_openai_api_key
488
+ settings.AZURE_OPENAI_ENDPOINT = azure_openai_endpoint
489
+ settings.OPENAI_API_VERSION = openai_api_version
490
+ settings.AZURE_DEPLOYMENT_NAME = azure_deployment_name
491
+ settings.AZURE_MODEL_NAME = openai_model_name
492
+ if azure_model_version is not None:
493
+ settings.AZURE_MODEL_VERSION = azure_model_version
494
+
495
+ handled, path, _ = edit_ctx.result
496
+
497
+ if not handled and save is not None:
498
+ # invalid --save format (unsupported)
499
+ print("Unsupported --save option. Use --save=dotenv[:path].")
500
+ elif path:
501
+ # persisted to a file
502
+ print(
503
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
504
+ )
505
+ else:
506
+ # updated in-memory & process env only
507
+ print(
508
+ "Settings updated for this session. To persist, use --save=dotenv[:path] "
509
+ "(default .env.local) or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
263
510
  )
264
-
265
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_AZURE_OPENAI, "YES")
266
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_LOCAL_MODEL, "NO")
267
511
 
268
512
  print(
269
- ":raising_hands: Congratulations! You're now using Azure OpenAI for all evals that require an LLM."
513
+ f":raising_hands: Congratulations! You're now using Azure OpenAI's `{escape(openai_model_name)}` for all evals that require an LLM."
270
514
  )
271
515
 
272
516
 
@@ -277,46 +521,115 @@ def set_azure_openai_embedding_env(
277
521
  "--embedding-deployment-name",
278
522
  help="Azure embedding deployment name",
279
523
  ),
524
+ save: Optional[str] = typer.Option(
525
+ None,
526
+ "--save",
527
+ help="Persist CLI parameters as environment variables in a dotenv file. "
528
+ "Usage: --save=dotenv[:path] (default: .env.local)",
529
+ ),
280
530
  ):
281
- clear_embedding_model_keys()
282
- KEY_FILE_HANDLER.write_key(
283
- EmbeddingKeyValues.AZURE_EMBEDDING_DEPLOYMENT_NAME,
284
- azure_embedding_deployment_name,
285
- )
286
- KEY_FILE_HANDLER.write_key(
287
- EmbeddingKeyValues.USE_AZURE_OPENAI_EMBEDDING, "YES"
288
- )
289
- KEY_FILE_HANDLER.write_key(EmbeddingKeyValues.USE_LOCAL_EMBEDDINGS, "NO")
531
+ settings = get_settings()
532
+ with settings.edit(save=save) as edit_ctx:
533
+ edit_ctx.switch_model_provider(
534
+ EmbeddingKeyValues.USE_AZURE_OPENAI_EMBEDDING
535
+ )
536
+ settings.AZURE_EMBEDDING_DEPLOYMENT_NAME = (
537
+ azure_embedding_deployment_name
538
+ )
539
+
540
+ handled, path, _ = edit_ctx.result
541
+
542
+ if not handled and save is not None:
543
+ # invalid --save format (unsupported)
544
+ print("Unsupported --save option. Use --save=dotenv[:path].")
545
+ elif path:
546
+ # persisted to a file
547
+ print(
548
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
549
+ )
550
+ else:
551
+ # updated in-memory & process env only
552
+ print(
553
+ "Settings updated for this session. To persist, use --save=dotenv[:path] "
554
+ "(default .env.local) or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
555
+ )
556
+
290
557
  print(
291
558
  ":raising_hands: Congratulations! You're now using Azure OpenAI Embeddings within DeepEval."
292
559
  )
293
560
 
294
561
 
295
562
  @app.command(name="unset-azure-openai")
296
- def unset_azure_openai_env():
297
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.AZURE_OPENAI_API_KEY)
298
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.AZURE_OPENAI_ENDPOINT)
299
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.OPENAI_API_VERSION)
300
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.AZURE_DEPLOYMENT_NAME)
301
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.AZURE_MODEL_NAME)
302
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.AZURE_MODEL_VERSION)
303
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.USE_AZURE_OPENAI)
304
-
305
- print(
306
- ":raising_hands: Congratulations! You're now using regular OpenAI for all evals that require an LLM."
563
+ def unset_azure_openai_env(
564
+ save: Optional[str] = typer.Option(
565
+ None,
566
+ "--save",
567
+ help="Remove only the Azure OpenAI–related environment variables from a dotenv file. "
568
+ "Usage: --save=dotenv[:path] (default: .env.local)",
307
569
  )
570
+ ):
571
+ settings = get_settings()
572
+ with settings.edit(save=save) as edit_ctx:
573
+ settings.AZURE_OPENAI_API_KEY = None
574
+ settings.AZURE_OPENAI_ENDPOINT = None
575
+ settings.OPENAI_API_VERSION = None
576
+ settings.AZURE_DEPLOYMENT_NAME = None
577
+ settings.AZURE_MODEL_NAME = None
578
+ settings.AZURE_MODEL_VERSION = None
579
+ settings.USE_AZURE_OPENAI = None
580
+
581
+ handled, path, _ = edit_ctx.result
582
+
583
+ if not handled and save is not None:
584
+ # invalid --save format (unsupported)
585
+ print("Unsupported --save option. Use --save=dotenv[:path].")
586
+ elif path:
587
+ # persisted to a file
588
+ print(f"Removed Azure OpenAI environment variables from {path}.")
589
+
590
+ if is_openai_configured():
591
+ print(
592
+ ":raised_hands: OpenAI will still be used by default because OPENAI_API_KEY is set."
593
+ )
594
+ else:
595
+ print(
596
+ "Azure OpenAI has been unset. No active provider is configured. Set one with the CLI, or add credentials to .env[.local]."
597
+ )
308
598
 
309
599
 
310
600
  @app.command(name="unset-azure-openai-embedding")
311
- def unset_azure_openai_embedding_env():
312
- KEY_FILE_HANDLER.remove_key(
313
- EmbeddingKeyValues.AZURE_EMBEDDING_DEPLOYMENT_NAME
314
- )
315
- KEY_FILE_HANDLER.remove_key(EmbeddingKeyValues.USE_AZURE_OPENAI_EMBEDDING)
601
+ def unset_azure_openai_embedding_env(
602
+ save: Optional[str] = typer.Option(
603
+ None,
604
+ "--save",
605
+ help="Remove only the Azure OpenAI embedding related environment variables from a dotenv file. "
606
+ "Usage: --save=dotenv[:path] (default: .env.local)",
607
+ ),
608
+ ):
609
+ settings = get_settings()
610
+ with settings.edit(save=save) as edit_ctx:
611
+ settings.AZURE_EMBEDDING_DEPLOYMENT_NAME = None
612
+ settings.USE_AZURE_OPENAI_EMBEDDING = None
613
+
614
+ handled, path, _ = edit_ctx.result
615
+
616
+ if not handled and save is not None:
617
+ # invalid --save format (unsupported)
618
+ print("Unsupported --save option. Use --save=dotenv[:path].")
619
+ elif path:
620
+ # persisted to a file
621
+ print(
622
+ f"Removed Azure OpenAI embedding environment variables from {path}."
623
+ )
316
624
 
317
- print(
318
- ":raising_hands: Congratulations! You're now using regular OpenAI embeddings for all evals that require text embeddings."
319
- )
625
+ if is_openai_configured():
626
+ print(
627
+ ":raised_hands: Regular OpenAI embeddings will still be used by default because OPENAI_API_KEY is set."
628
+ )
629
+ else:
630
+ print(
631
+ "The Azure OpenAI embedding model configuration has been removed. No model is currently configured, but you can set one with the CLI or add credentials to .env[.local]."
632
+ )
320
633
 
321
634
 
322
635
  #############################################
@@ -333,27 +646,75 @@ def set_ollama_model_env(
333
646
  "--base-url",
334
647
  help="Base URL for the local model API",
335
648
  ),
649
+ save: Optional[str] = typer.Option(
650
+ None,
651
+ "--save",
652
+ help="Persist CLI parameters as environment variables in a dotenv file. "
653
+ "Usage: --save=dotenv[:path] (default: .env.local)",
654
+ ),
336
655
  ):
337
- clear_evaluation_model_keys()
338
- KEY_FILE_HANDLER.write_key(ModelKeyValues.LOCAL_MODEL_NAME, model_name)
339
- KEY_FILE_HANDLER.write_key(ModelKeyValues.LOCAL_MODEL_BASE_URL, base_url)
340
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_LOCAL_MODEL, "YES")
341
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_AZURE_OPENAI, "NO")
342
- KEY_FILE_HANDLER.write_key(ModelKeyValues.LOCAL_MODEL_API_KEY, "ollama")
656
+ settings = get_settings()
657
+ with settings.edit(save=save) as edit_ctx:
658
+ edit_ctx.switch_model_provider(ModelKeyValues.USE_LOCAL_MODEL)
659
+ settings.LOCAL_MODEL_API_KEY = "ollama"
660
+ settings.LOCAL_MODEL_NAME = model_name
661
+ settings.LOCAL_MODEL_BASE_URL = base_url
662
+
663
+ handled, path, _ = edit_ctx.result
664
+
665
+ if not handled and save is not None:
666
+ # invalid --save format (unsupported)
667
+ print("Unsupported --save option. Use --save=dotenv[:path].")
668
+ elif path:
669
+ # persisted to a file
670
+ print(
671
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
672
+ )
673
+ else:
674
+ # updated in-memory & process env only
675
+ print(
676
+ "Settings updated for this session. To persist, use --save=dotenv[:path] "
677
+ "(default .env.local) or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
678
+ )
679
+
343
680
  print(
344
- ":raising_hands: Congratulations! You're now using a local Ollama model for all evals that require an LLM."
681
+ f":raising_hands: Congratulations! You're now using a local Ollama model `{escape(model_name)}` for all evals that require an LLM."
345
682
  )
346
683
 
347
684
 
348
685
  @app.command(name="unset-ollama")
349
- def unset_ollama_model_env():
350
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.LOCAL_MODEL_NAME)
351
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.LOCAL_MODEL_BASE_URL)
352
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.USE_LOCAL_MODEL)
353
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.LOCAL_MODEL_API_KEY)
354
- print(
355
- ":raising_hands: Congratulations! You're now using regular OpenAI for all evals that require an LLM."
356
- )
686
+ def unset_ollama_model_env(
687
+ save: Optional[str] = typer.Option(
688
+ None,
689
+ "--save",
690
+ help="Remove only the Ollama related environment variables from a dotenv file. "
691
+ "Usage: --save=dotenv[:path] (default: .env.local)",
692
+ ),
693
+ ):
694
+ settings = get_settings()
695
+ with settings.edit(save=save) as edit_ctx:
696
+ settings.LOCAL_MODEL_API_KEY = None
697
+ settings.LOCAL_MODEL_NAME = None
698
+ settings.LOCAL_MODEL_BASE_URL = None
699
+ settings.USE_LOCAL_MODEL = None
700
+
701
+ handled, path, _ = edit_ctx.result
702
+
703
+ if not handled and save is not None:
704
+ # invalid --save format (unsupported)
705
+ print("Unsupported --save option. Use --save=dotenv[:path].")
706
+ elif path:
707
+ # persisted to a file
708
+ print(f"Removed local Ollama environment variables from {path}.")
709
+
710
+ if is_openai_configured():
711
+ print(
712
+ ":raised_hands: OpenAI will still be used by default because OPENAI_API_KEY is set."
713
+ )
714
+ else:
715
+ print(
716
+ "The local Ollama model configuration has been removed. No model is currently configured, but you can set one with the CLI or add credentials to .env[.local]."
717
+ )
357
718
 
358
719
 
359
720
  @app.command(name="set-ollama-embeddings")
@@ -367,37 +728,78 @@ def set_ollama_embeddings_env(
367
728
  "--base-url",
368
729
  help="Base URL for the Ollama embedding model API",
369
730
  ),
731
+ save: Optional[str] = typer.Option(
732
+ None,
733
+ "--save",
734
+ help="Persist CLI parameters as environment variables in a dotenv file. "
735
+ "Usage: --save=dotenv[:path] (default: .env.local)",
736
+ ),
370
737
  ):
371
- clear_embedding_model_keys()
372
- KEY_FILE_HANDLER.write_key(
373
- EmbeddingKeyValues.LOCAL_EMBEDDING_MODEL_NAME, model_name
374
- )
375
- KEY_FILE_HANDLER.write_key(
376
- EmbeddingKeyValues.LOCAL_EMBEDDING_BASE_URL, base_url
377
- )
378
- KEY_FILE_HANDLER.write_key(
379
- EmbeddingKeyValues.LOCAL_EMBEDDING_API_KEY, "ollama"
380
- )
381
- KEY_FILE_HANDLER.write_key(EmbeddingKeyValues.USE_LOCAL_EMBEDDINGS, "YES")
382
- KEY_FILE_HANDLER.write_key(
383
- EmbeddingKeyValues.USE_AZURE_OPENAI_EMBEDDING, "NO"
384
- )
738
+ settings = get_settings()
739
+ with settings.edit(save=save) as edit_ctx:
740
+ edit_ctx.switch_model_provider(EmbeddingKeyValues.USE_LOCAL_EMBEDDINGS)
741
+ settings.LOCAL_EMBEDDING_API_KEY = "ollama"
742
+ settings.LOCAL_EMBEDDING_MODEL_NAME = model_name
743
+ settings.LOCAL_EMBEDDING_BASE_URL = base_url
744
+
745
+ handled, path, _ = edit_ctx.result
746
+
747
+ if not handled and save is not None:
748
+ # invalid --save format (unsupported)
749
+ print("Unsupported --save option. Use --save=dotenv[:path].")
750
+ elif path:
751
+ # persisted to a file
752
+ print(
753
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
754
+ )
755
+ else:
756
+ # updated in-memory & process env only
757
+ print(
758
+ "Settings updated for this session. To persist, use --save=dotenv[:path] "
759
+ "(default .env.local) or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
760
+ )
385
761
 
386
762
  print(
387
- ":raising_hands: Congratulations! You're now using Ollama embeddings for all evals that require text embeddings."
763
+ f":raising_hands: Congratulations! You're now using the Ollama embedding model `{escape(model_name)}` for all evals that require text embeddings."
388
764
  )
389
765
 
390
766
 
391
767
  @app.command(name="unset-ollama-embeddings")
392
- def unset_ollama_embeddings_env():
393
- KEY_FILE_HANDLER.remove_key(EmbeddingKeyValues.LOCAL_EMBEDDING_MODEL_NAME)
394
- KEY_FILE_HANDLER.remove_key(EmbeddingKeyValues.LOCAL_EMBEDDING_BASE_URL)
395
- KEY_FILE_HANDLER.remove_key(EmbeddingKeyValues.LOCAL_EMBEDDING_API_KEY)
396
- KEY_FILE_HANDLER.remove_key(EmbeddingKeyValues.USE_LOCAL_EMBEDDINGS)
768
+ def unset_ollama_embeddings_env(
769
+ save: Optional[str] = typer.Option(
770
+ None,
771
+ "--save",
772
+ help="Remove only the Ollama embedding related environment variables from a dotenv file. "
773
+ "Usage: --save=dotenv[:path] (default: .env.local)",
774
+ ),
775
+ ):
397
776
 
398
- print(
399
- ":raising_hands: Congratulations! You're now using regular OpenAI embeddings for all evals that require text embeddings."
400
- )
777
+ settings = get_settings()
778
+ with settings.edit(save=save) as edit_ctx:
779
+ settings.LOCAL_EMBEDDING_API_KEY = None
780
+ settings.LOCAL_EMBEDDING_MODEL_NAME = None
781
+ settings.LOCAL_EMBEDDING_BASE_URL = None
782
+ settings.USE_LOCAL_EMBEDDINGS = None
783
+
784
+ handled, path, _ = edit_ctx.result
785
+
786
+ if not handled and save is not None:
787
+ # invalid --save format (unsupported)
788
+ print("Unsupported --save option. Use --save=dotenv[:path].")
789
+ elif path:
790
+ # persisted to a file
791
+ print(
792
+ f"Removed local Ollama embedding environment variables from {path}."
793
+ )
794
+
795
+ if is_openai_configured():
796
+ print(
797
+ ":raised_hands: Regular OpenAI embeddings will still be used by default because OPENAI_API_KEY is set."
798
+ )
799
+ else:
800
+ print(
801
+ "The local Ollama embedding model configuration has been removed. No model is currently configured, but you can set one with the CLI or add credentials to .env[.local]."
802
+ )
401
803
 
402
804
 
403
805
  #############################################
@@ -414,39 +816,88 @@ def set_local_model_env(
414
816
  ..., "--base-url", help="Base URL for the local model API"
415
817
  ),
416
818
  api_key: Optional[str] = typer.Option(
417
- None, "--api-key", help="API key for the local model (if required)"
819
+ None,
820
+ "--api-key",
821
+ help="API key for the local model. Persisted to dotenv if --save is used; never written to the legacy JSON keystore.",
418
822
  ),
419
- format: Optional[str] = typer.Option(
823
+ model_format: Optional[str] = typer.Option(
420
824
  "json",
421
825
  "--format",
422
826
  help="Format of the response from the local model (default: json)",
423
827
  ),
828
+ save: Optional[str] = typer.Option(
829
+ None,
830
+ "--save",
831
+ help="Persist CLI parameters as environment variables in a dotenv file. "
832
+ "Usage: --save=dotenv[:path] (default: .env.local)",
833
+ ),
424
834
  ):
425
- clear_evaluation_model_keys()
426
- KEY_FILE_HANDLER.write_key(ModelKeyValues.LOCAL_MODEL_NAME, model_name)
427
- KEY_FILE_HANDLER.write_key(ModelKeyValues.LOCAL_MODEL_BASE_URL, base_url)
428
- if api_key:
429
- KEY_FILE_HANDLER.write_key(ModelKeyValues.LOCAL_MODEL_API_KEY, api_key)
430
- if format:
431
- KEY_FILE_HANDLER.write_key(ModelKeyValues.LOCAL_MODEL_FORMAT, format)
432
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_LOCAL_MODEL, "YES")
433
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_AZURE_OPENAI, "NO")
835
+ settings = get_settings()
836
+ with settings.edit(save=save) as edit_ctx:
837
+ edit_ctx.switch_model_provider(ModelKeyValues.USE_LOCAL_MODEL)
838
+ settings.LOCAL_MODEL_NAME = model_name
839
+ settings.LOCAL_MODEL_BASE_URL = base_url
840
+ if model_format:
841
+ settings.LOCAL_MODEL_FORMAT = model_format
842
+ if api_key:
843
+ settings.LOCAL_MODEL_API_KEY = api_key
844
+
845
+ handled, path, _ = edit_ctx.result
846
+
847
+ if not handled and save is not None:
848
+ # invalid --save format (unsupported)
849
+ print("Unsupported --save option. Use --save=dotenv[:path].")
850
+ elif path:
851
+ # persisted to a file
852
+ print(
853
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
854
+ )
855
+ else:
856
+ # updated in-memory & process env only
857
+ print(
858
+ "Settings updated for this session. To persist, use --save=dotenv[:path] "
859
+ "(default .env.local) or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
860
+ )
861
+
434
862
  print(
435
- ":raising_hands: Congratulations! You're now using a local model for all evals that require an LLM."
863
+ f":raising_hands: Congratulations! You're now using a local model `{escape(model_name)}` for all evals that require an LLM."
436
864
  )
437
865
 
438
866
 
439
867
  @app.command(name="unset-local-model")
440
- def unset_local_model_env():
441
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.LOCAL_MODEL_NAME)
442
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.LOCAL_MODEL_BASE_URL)
443
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.LOCAL_MODEL_API_KEY)
444
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.LOCAL_MODEL_FORMAT)
445
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.USE_LOCAL_MODEL)
446
-
447
- print(
448
- ":raising_hands: Congratulations! You're now using regular OpenAI for all evals that require an LLM."
449
- )
868
+ def unset_local_model_env(
869
+ save: Optional[str] = typer.Option(
870
+ None,
871
+ "--save",
872
+ help="Remove only the local model related environment variables from a dotenv file. "
873
+ "Usage: --save=dotenv[:path] (default: .env.local)",
874
+ ),
875
+ ):
876
+ settings = get_settings()
877
+ with settings.edit(save=save) as edit_ctx:
878
+ settings.LOCAL_MODEL_API_KEY = None
879
+ settings.LOCAL_MODEL_NAME = None
880
+ settings.LOCAL_MODEL_BASE_URL = None
881
+ settings.LOCAL_MODEL_FORMAT = None
882
+ settings.USE_LOCAL_MODEL = None
883
+
884
+ handled, path, _ = edit_ctx.result
885
+
886
+ if not handled and save is not None:
887
+ # invalid --save format (unsupported)
888
+ print("Unsupported --save option. Use --save=dotenv[:path].")
889
+ elif path:
890
+ # persisted to a file
891
+ print(f"Removed local model environment variables from {path}.")
892
+
893
+ if is_openai_configured():
894
+ print(
895
+ ":raised_hands: OpenAI will still be used by default because OPENAI_API_KEY is set."
896
+ )
897
+ else:
898
+ print(
899
+ "The local model configuration has been removed. No model is currently configured, but you can set one with the CLI or add credentials to .env[.local]."
900
+ )
450
901
 
451
902
 
452
903
  #############################################
@@ -460,31 +911,82 @@ def set_grok_model_env(
460
911
  ..., "--model-name", help="Name of the Grok model"
461
912
  ),
462
913
  api_key: str = typer.Option(
463
- ..., "--api-key", help="API key for the Grok model"
914
+ ...,
915
+ "--api-key",
916
+ help="API key for the Grok model. Persisted to dotenv if --save is used; never written to the legacy JSON keystore.",
464
917
  ),
465
918
  temperature: float = typer.Option(
466
919
  0, "--temperature", help="Temperature for the Grok model"
467
920
  ),
921
+ save: Optional[str] = typer.Option(
922
+ None,
923
+ "--save",
924
+ help="Persist CLI parameters as environment variables in a dotenv file. "
925
+ "Usage: --save=dotenv[:path] (default: .env.local)",
926
+ ),
468
927
  ):
469
- clear_evaluation_model_keys()
470
- KEY_FILE_HANDLER.write_key(ModelKeyValues.GROK_MODEL_NAME, model_name)
471
- KEY_FILE_HANDLER.write_key(ModelKeyValues.GROK_API_KEY, api_key)
472
- KEY_FILE_HANDLER.write_key(ModelKeyValues.TEMPERATURE, str(temperature))
473
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_GROK_MODEL, "YES")
928
+ settings = get_settings()
929
+ with settings.edit(save=save) as edit_ctx:
930
+ edit_ctx.switch_model_provider(ModelKeyValues.USE_GROK_MODEL)
931
+ settings.GROK_API_KEY = api_key
932
+ settings.GROK_MODEL_NAME = model_name
933
+ settings.TEMPERATURE = temperature
934
+
935
+ handled, path, _ = edit_ctx.result
936
+
937
+ if not handled and save is not None:
938
+ # invalid --save format (unsupported)
939
+ print("Unsupported --save option. Use --save=dotenv[:path].")
940
+ elif path:
941
+ # persisted to a file
942
+ print(
943
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
944
+ )
945
+ else:
946
+ # updated in-memory & process env only
947
+ print(
948
+ "Settings updated for this session. To persist, use --save=dotenv[:path] "
949
+ "(default .env.local) or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
950
+ )
951
+
474
952
  print(
475
- ":raising_hands: Congratulations! You're now using a Grok model for all evals that require an LLM."
953
+ f":raising_hands: Congratulations! You're now using a Grok's `{escape(model_name)}` for all evals that require an LLM."
476
954
  )
477
955
 
478
956
 
479
957
  @app.command(name="unset-grok")
480
- def unset_grok_model_env():
481
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.GROK_MODEL_NAME)
482
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.GROK_API_KEY)
483
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.TEMPERATURE)
484
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.USE_GROK_MODEL)
485
- print(
486
- ":raising_hands: Congratulations! You're now using regular OpenAI for all evals that require an LLM."
487
- )
958
+ def unset_grok_model_env(
959
+ save: Optional[str] = typer.Option(
960
+ None,
961
+ "--save",
962
+ help="Remove only the Grok model related environment variables from a dotenv file. "
963
+ "Usage: --save=dotenv[:path] (default: .env.local)",
964
+ ),
965
+ ):
966
+ settings = get_settings()
967
+ with settings.edit(save=save) as edit_ctx:
968
+ settings.GROK_API_KEY = None
969
+ settings.GROK_MODEL_NAME = None
970
+ settings.TEMPERATURE = None
971
+ settings.USE_GROK_MODEL = None
972
+
973
+ handled, path, _ = edit_ctx.result
974
+
975
+ if not handled and save is not None:
976
+ # invalid --save format (unsupported)
977
+ print("Unsupported --save option. Use --save=dotenv[:path].")
978
+ elif path:
979
+ # persisted to a file
980
+ print(f"Removed Grok model environment variables from {path}.")
981
+
982
+ if is_openai_configured():
983
+ print(
984
+ ":raised_hands: OpenAI will still be used by default because OPENAI_API_KEY is set."
985
+ )
986
+ else:
987
+ print(
988
+ "The Grok model configuration has been removed. No model is currently configured, but you can set one with the CLI or add credentials to .env[.local]."
989
+ )
488
990
 
489
991
 
490
992
  #############################################
@@ -498,31 +1000,82 @@ def set_moonshot_model_env(
498
1000
  ..., "--model-name", help="Name of the Moonshot model"
499
1001
  ),
500
1002
  api_key: str = typer.Option(
501
- ..., "--api-key", help="API key for the Moonshot model"
1003
+ ...,
1004
+ "--api-key",
1005
+ help="API key for the Moonshot model. Persisted to dotenv if --save is used; never written to the legacy JSON keystore.",
502
1006
  ),
503
1007
  temperature: float = typer.Option(
504
1008
  0, "--temperature", help="Temperature for the Moonshot model"
505
1009
  ),
1010
+ save: Optional[str] = typer.Option(
1011
+ None,
1012
+ "--save",
1013
+ help="Persist CLI parameters as environment variables in a dotenv file. "
1014
+ "Usage: --save=dotenv[:path] (default: .env.local)",
1015
+ ),
506
1016
  ):
507
- clear_evaluation_model_keys()
508
- KEY_FILE_HANDLER.write_key(ModelKeyValues.MOONSHOT_MODEL_NAME, model_name)
509
- KEY_FILE_HANDLER.write_key(ModelKeyValues.MOONSHOT_API_KEY, api_key)
510
- KEY_FILE_HANDLER.write_key(ModelKeyValues.TEMPERATURE, str(temperature))
511
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_MOONSHOT_MODEL, "YES")
1017
+ settings = get_settings()
1018
+ with settings.edit(save=save) as edit_ctx:
1019
+ edit_ctx.switch_model_provider(ModelKeyValues.USE_MOONSHOT_MODEL)
1020
+ settings.MOONSHOT_API_KEY = api_key
1021
+ settings.MOONSHOT_MODEL_NAME = model_name
1022
+ settings.TEMPERATURE = temperature
1023
+
1024
+ handled, path, _ = edit_ctx.result
1025
+
1026
+ if not handled and save is not None:
1027
+ # invalid --save format (unsupported)
1028
+ print("Unsupported --save option. Use --save=dotenv[:path].")
1029
+ elif path:
1030
+ # persisted to a file
1031
+ print(
1032
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
1033
+ )
1034
+ else:
1035
+ # updated in-memory & process env only
1036
+ print(
1037
+ "Settings updated for this session. To persist, use --save=dotenv[:path] "
1038
+ "(default .env.local) or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
1039
+ )
1040
+
512
1041
  print(
513
- ":raising_hands: Congratulations! You're now using a Moonshot model for all evals that require an LLM."
1042
+ f":raising_hands: Congratulations! You're now using Moonshot's `{escape(model_name)}` for all evals that require an LLM."
514
1043
  )
515
1044
 
516
1045
 
517
1046
  @app.command(name="unset-moonshot")
518
- def unset_moonshot_model_env():
519
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.MOONSHOT_MODEL_NAME)
520
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.MOONSHOT_API_KEY)
521
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.TEMPERATURE)
522
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.USE_MOONSHOT_MODEL)
523
- print(
524
- ":raising_hands: Congratulations! You're now using regular OpenAI for all evals that require an LLM."
525
- )
1047
+ def unset_moonshot_model_env(
1048
+ save: Optional[str] = typer.Option(
1049
+ None,
1050
+ "--save",
1051
+ help="Remove only the Moonshot model related environment variables from a dotenv file. "
1052
+ "Usage: --save=dotenv[:path] (default: .env.local)",
1053
+ ),
1054
+ ):
1055
+ settings = get_settings()
1056
+ with settings.edit(save=save) as edit_ctx:
1057
+ settings.MOONSHOT_API_KEY = None
1058
+ settings.MOONSHOT_MODEL_NAME = None
1059
+ settings.TEMPERATURE = None
1060
+ settings.USE_MOONSHOT_MODEL = None
1061
+
1062
+ handled, path, _ = edit_ctx.result
1063
+
1064
+ if not handled and save is not None:
1065
+ # invalid --save format (unsupported)
1066
+ print("Unsupported --save option. Use --save=dotenv[:path].")
1067
+ elif path:
1068
+ # persisted to a file
1069
+ print(f"Removed Moonshot model environment variables from {path}.")
1070
+
1071
+ if is_openai_configured():
1072
+ print(
1073
+ ":raised_hands: OpenAI will still be used by default because OPENAI_API_KEY is set."
1074
+ )
1075
+ else:
1076
+ print(
1077
+ "The Moonshot model configuration has been removed. No model is currently configured, but you can set one with the CLI or add credentials to .env[.local]."
1078
+ )
526
1079
 
527
1080
 
528
1081
  #############################################
@@ -536,31 +1089,82 @@ def set_deepseek_model_env(
536
1089
  ..., "--model-name", help="Name of the DeepSeek model"
537
1090
  ),
538
1091
  api_key: str = typer.Option(
539
- ..., "--api-key", help="API key for the DeepSeek model"
1092
+ ...,
1093
+ "--api-key",
1094
+ help="API key for the DeepSeek model. Persisted to dotenv if --save is used; never written to the legacy JSON keystore.",
540
1095
  ),
541
1096
  temperature: float = typer.Option(
542
1097
  0, "--temperature", help="Temperature for the DeepSeek model"
543
1098
  ),
1099
+ save: Optional[str] = typer.Option(
1100
+ None,
1101
+ "--save",
1102
+ help="Persist CLI parameters as environment variables in a dotenv file. "
1103
+ "Usage: --save=dotenv[:path] (default: .env.local)",
1104
+ ),
544
1105
  ):
545
- clear_evaluation_model_keys()
546
- KEY_FILE_HANDLER.write_key(ModelKeyValues.DEEPSEEK_MODEL_NAME, model_name)
547
- KEY_FILE_HANDLER.write_key(ModelKeyValues.DEEPSEEK_API_KEY, api_key)
548
- KEY_FILE_HANDLER.write_key(ModelKeyValues.TEMPERATURE, str(temperature))
549
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_DEEPSEEK_MODEL, "YES")
1106
+ settings = get_settings()
1107
+ with settings.edit(save=save) as edit_ctx:
1108
+ edit_ctx.switch_model_provider(ModelKeyValues.USE_DEEPSEEK_MODEL)
1109
+ settings.DEEPSEEK_API_KEY = api_key
1110
+ settings.DEEPSEEK_MODEL_NAME = model_name
1111
+ settings.TEMPERATURE = temperature
1112
+
1113
+ handled, path, _ = edit_ctx.result
1114
+
1115
+ if not handled and save is not None:
1116
+ # invalid --save format (unsupported)
1117
+ print("Unsupported --save option. Use --save=dotenv[:path].")
1118
+ elif path:
1119
+ # persisted to a file
1120
+ print(
1121
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
1122
+ )
1123
+ else:
1124
+ # updated in-memory & process env only
1125
+ print(
1126
+ "Settings updated for this session. To persist, use --save=dotenv[:path] "
1127
+ "(default .env.local) or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
1128
+ )
1129
+
550
1130
  print(
551
- ":raising_hands: Congratulations! You're now using a DeepSeek model for all evals that require an LLM."
1131
+ f":raising_hands: Congratulations! You're now using DeepSeek's `{escape(model_name)}` for all evals that require an LLM."
552
1132
  )
553
1133
 
554
1134
 
555
1135
  @app.command(name="unset-deepseek")
556
- def unset_deepseek_model_env():
557
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.DEEPSEEK_MODEL_NAME)
558
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.DEEPSEEK_API_KEY)
559
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.TEMPERATURE)
560
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.USE_DEEPSEEK_MODEL)
561
- print(
562
- ":raising_hands: Congratulations! You're now using regular OpenAI for all evals that require an LLM."
563
- )
1136
+ def unset_deepseek_model_env(
1137
+ save: Optional[str] = typer.Option(
1138
+ None,
1139
+ "--save",
1140
+ help="Remove only the DeepSeek model related environment variables from a dotenv file. "
1141
+ "Usage: --save=dotenv[:path] (default: .env.local)",
1142
+ ),
1143
+ ):
1144
+ settings = get_settings()
1145
+ with settings.edit(save=save) as edit_ctx:
1146
+ settings.DEEPSEEK_API_KEY = None
1147
+ settings.DEEPSEEK_MODEL_NAME = None
1148
+ settings.TEMPERATURE = None
1149
+ settings.USE_DEEPSEEK_MODEL = None
1150
+
1151
+ handled, path, _ = edit_ctx.result
1152
+
1153
+ if not handled and save is not None:
1154
+ # invalid --save format (unsupported)
1155
+ print("Unsupported --save option. Use --save=dotenv[:path].")
1156
+ elif path:
1157
+ # persisted to a file
1158
+ print(f"Removed DeepSeek model environment variables from {path}.")
1159
+
1160
+ if is_openai_configured():
1161
+ print(
1162
+ ":raised_hands: OpenAI will still be used by default because OPENAI_API_KEY is set."
1163
+ )
1164
+ else:
1165
+ print(
1166
+ "The DeepSeek model configuration has been removed. No model is currently configured, but you can set one with the CLI or add credentials to .env[.local]."
1167
+ )
564
1168
 
565
1169
 
566
1170
  #############################################
@@ -577,45 +1181,84 @@ def set_local_embeddings_env(
577
1181
  ..., "--base-url", help="Base URL for the local embeddings API"
578
1182
  ),
579
1183
  api_key: Optional[str] = typer.Option(
580
- None, "--api-key", help="API key for the local embeddings (if required)"
1184
+ None,
1185
+ "--api-key",
1186
+ help="API key for the local embeddings. Persisted to dotenv if --save is used; never written to the legacy JSON keystore.",
1187
+ ),
1188
+ save: Optional[str] = typer.Option(
1189
+ None,
1190
+ "--save",
1191
+ help="Persist CLI parameters as environment variables in a dotenv file. "
1192
+ "Usage: --save=dotenv[:path] (default: .env.local)",
581
1193
  ),
582
1194
  ):
583
- clear_embedding_model_keys()
584
- KEY_FILE_HANDLER.write_key(
585
- EmbeddingKeyValues.LOCAL_EMBEDDING_MODEL_NAME, model_name
586
- )
587
- KEY_FILE_HANDLER.write_key(
588
- EmbeddingKeyValues.LOCAL_EMBEDDING_BASE_URL, base_url
589
- )
590
- if api_key:
591
- KEY_FILE_HANDLER.write_key(
592
- EmbeddingKeyValues.LOCAL_EMBEDDING_API_KEY, api_key
1195
+ settings = get_settings()
1196
+ with settings.edit(save=save) as edit_ctx:
1197
+ edit_ctx.switch_model_provider(EmbeddingKeyValues.USE_LOCAL_EMBEDDINGS)
1198
+ settings.LOCAL_EMBEDDING_MODEL_NAME = model_name
1199
+ settings.LOCAL_EMBEDDING_BASE_URL = base_url
1200
+ if api_key:
1201
+ settings.LOCAL_EMBEDDING_API_KEY = api_key
1202
+
1203
+ handled, path, _ = edit_ctx.result
1204
+
1205
+ if not handled and save is not None:
1206
+ # invalid --save format (unsupported)
1207
+ print("Unsupported --save option. Use --save=dotenv[:path].")
1208
+ elif path:
1209
+ # persisted to a file
1210
+ print(
1211
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
1212
+ )
1213
+ else:
1214
+ # updated in-memory & process env only
1215
+ print(
1216
+ "Settings updated for this session. To persist, use --save=dotenv[:path] "
1217
+ "(default .env.local) or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
593
1218
  )
594
-
595
- KEY_FILE_HANDLER.write_key(EmbeddingKeyValues.USE_LOCAL_EMBEDDINGS, "YES")
596
- KEY_FILE_HANDLER.write_key(
597
- EmbeddingKeyValues.USE_AZURE_OPENAI_EMBEDDING, "NO"
598
- )
599
1219
 
600
1220
  print(
601
- ":raising_hands: Congratulations! You're now using local embeddings for all evals that require text embeddings."
1221
+ f":raising_hands: Congratulations! You're now using the local embedding model `{escape(model_name)}` for all evals that require text embeddings."
602
1222
  )
603
1223
 
604
1224
 
605
1225
  @app.command(name="unset-local-embeddings")
606
- def unset_local_embeddings_env():
607
- KEY_FILE_HANDLER.remove_key(EmbeddingKeyValues.LOCAL_EMBEDDING_MODEL_NAME)
608
- KEY_FILE_HANDLER.remove_key(EmbeddingKeyValues.LOCAL_EMBEDDING_BASE_URL)
609
- KEY_FILE_HANDLER.remove_key(EmbeddingKeyValues.LOCAL_EMBEDDING_API_KEY)
610
- KEY_FILE_HANDLER.remove_key(EmbeddingKeyValues.USE_LOCAL_EMBEDDINGS)
611
-
612
- print(
613
- ":raising_hands: Congratulations! You're now using regular OpenAI embeddings for all evals that require text embeddings."
614
- )
1226
+ def unset_local_embeddings_env(
1227
+ save: Optional[str] = typer.Option(
1228
+ None,
1229
+ "--save",
1230
+ help="Remove only the local embedding related environment variables from a dotenv file. "
1231
+ "Usage: --save=dotenv[:path] (default: .env.local)",
1232
+ ),
1233
+ ):
1234
+ settings = get_settings()
1235
+ with settings.edit(save=save) as edit_ctx:
1236
+ settings.LOCAL_EMBEDDING_API_KEY = None
1237
+ settings.LOCAL_EMBEDDING_MODEL_NAME = None
1238
+ settings.LOCAL_EMBEDDING_BASE_URL = None
1239
+ settings.USE_LOCAL_EMBEDDINGS = None
1240
+
1241
+ handled, path, _ = edit_ctx.result
1242
+
1243
+ if not handled and save is not None:
1244
+ # invalid --save format (unsupported)
1245
+ print("Unsupported --save option. Use --save=dotenv[:path].")
1246
+ elif path:
1247
+ # persisted to a file
1248
+ print(f"Removed local embedding environment variables from {path}.")
1249
+
1250
+ if is_openai_configured():
1251
+ print(
1252
+ ":raised_hands: OpenAI will still be used by default because OPENAI_API_KEY is set."
1253
+ )
1254
+ else:
1255
+ print(
1256
+ "The local embeddings model configuration has been removed. No model is currently configured, but you can set one with the CLI or add credentials to .env[.local]."
1257
+ )
615
1258
 
616
1259
 
617
1260
  #############################################
618
- # Ollama Integration ########################
1261
+ # Gemini Integration ########################
619
1262
  #############################################
620
1263
 
621
1264
 
@@ -625,7 +1268,9 @@ def set_gemini_model_env(
625
1268
  None, "--model-name", help="Gemini Model name"
626
1269
  ),
627
1270
  google_api_key: Optional[str] = typer.Option(
628
- None, "--google-api-key", help="Google API Key for Gemini"
1271
+ None,
1272
+ "--google-api-key",
1273
+ help="Google API Key for Gemini",
629
1274
  ),
630
1275
  google_cloud_project: Optional[str] = typer.Option(
631
1276
  None, "--project-id", help="Google Cloud project ID"
@@ -633,8 +1278,13 @@ def set_gemini_model_env(
633
1278
  google_cloud_location: Optional[str] = typer.Option(
634
1279
  None, "--location", help="Google Cloud location"
635
1280
  ),
1281
+ save: Optional[str] = typer.Option(
1282
+ None,
1283
+ "--save",
1284
+ help="Persist CLI parameters as environment variables in a dotenv file. "
1285
+ "Usage: --save=dotenv[:path] (default: .env.local)",
1286
+ ),
636
1287
  ):
637
- clear_evaluation_model_keys()
638
1288
  if not google_api_key and not (
639
1289
  google_cloud_project and google_cloud_location
640
1290
  ):
@@ -643,81 +1293,172 @@ def set_gemini_model_env(
643
1293
  err=True,
644
1294
  )
645
1295
  raise typer.Exit(code=1)
646
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_GEMINI_MODEL, "YES")
647
- if model_name is not None:
648
- KEY_FILE_HANDLER.write_key(ModelKeyValues.GEMINI_MODEL_NAME, model_name)
649
- if google_api_key is not None:
650
- KEY_FILE_HANDLER.write_key(
651
- ModelKeyValues.GOOGLE_API_KEY, google_api_key
1296
+
1297
+ settings = get_settings()
1298
+ with settings.edit(save=save) as edit_ctx:
1299
+ edit_ctx.switch_model_provider(ModelKeyValues.USE_GEMINI_MODEL)
1300
+
1301
+ if google_api_key is not None:
1302
+ settings.GOOGLE_API_KEY = google_api_key
1303
+ settings.GOOGLE_GENAI_USE_VERTEXAI = False
1304
+ else:
1305
+ settings.GOOGLE_GENAI_USE_VERTEXAI = True
1306
+ if google_cloud_project:
1307
+ settings.GOOGLE_CLOUD_PROJECT = google_cloud_project
1308
+ if google_cloud_location:
1309
+ settings.GOOGLE_CLOUD_LOCATION = google_cloud_location
1310
+ if model_name:
1311
+ settings.GEMINI_MODEL_NAME = model_name
1312
+
1313
+ handled, path, _ = edit_ctx.result
1314
+
1315
+ if not handled and save is not None:
1316
+ # invalid --save format (unsupported)
1317
+ print("Unsupported --save option. Use --save=dotenv[:path].")
1318
+ elif path:
1319
+ # persisted to a file
1320
+ print(
1321
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
652
1322
  )
653
1323
  else:
654
- KEY_FILE_HANDLER.write_key(
655
- ModelKeyValues.GOOGLE_GENAI_USE_VERTEXAI, "YES"
1324
+ # updated in-memory & process env only
1325
+ print(
1326
+ "Settings updated for this session. To persist, use --save=dotenv[:path] "
1327
+ "(default .env.local) or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
656
1328
  )
657
1329
 
658
- if google_cloud_project is not None:
659
- KEY_FILE_HANDLER.write_key(
660
- ModelKeyValues.GOOGLE_CLOUD_PROJECT, google_cloud_project
1330
+ _model_name = (
1331
+ model_name if model_name is not None else settings.GEMINI_MODEL_NAME
1332
+ )
1333
+ if _model_name is not None:
1334
+ print(
1335
+ f":raising_hands: Congratulations! You're now using Gemini's `{escape(_model_name)}` for all evals that require an LLM."
661
1336
  )
662
- if google_cloud_location is not None:
663
- KEY_FILE_HANDLER.write_key(
664
- ModelKeyValues.GOOGLE_CLOUD_LOCATION, google_cloud_location
1337
+ else:
1338
+ print(
1339
+ f":raising_hands: Congratulations! You're now using Gemini's model for all evals that require an LLM."
665
1340
  )
666
- print(
667
- ":raising_hands: Congratulations! You're now using a Gemini model for all evals that require an LLM."
668
- )
669
1341
 
670
1342
 
671
1343
  @app.command(name="unset-gemini")
672
- def unset_gemini_model_env():
673
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.USE_GEMINI_MODEL)
674
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.GEMINI_MODEL_NAME)
675
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.GOOGLE_API_KEY)
676
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.GOOGLE_CLOUD_PROJECT)
677
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.GOOGLE_CLOUD_LOCATION)
678
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.GOOGLE_GENAI_USE_VERTEXAI)
679
-
680
- print(
681
- ":raised_hands: Gemini model has been unset. You're now using regular OpenAI for all evals that require an LLM."
682
- )
1344
+ def unset_gemini_model_env(
1345
+ save: Optional[str] = typer.Option(
1346
+ None,
1347
+ "--save",
1348
+ help="Remove only the Gemini related environment variables from a dotenv file. "
1349
+ "Usage: --save=dotenv[:path] (default: .env.local)",
1350
+ ),
1351
+ ):
1352
+ settings = get_settings()
1353
+ with settings.edit(save=save) as edit_ctx:
1354
+ settings.GOOGLE_API_KEY = None
1355
+ settings.GOOGLE_GENAI_USE_VERTEXAI = None
1356
+ settings.GOOGLE_CLOUD_PROJECT = None
1357
+ settings.GOOGLE_CLOUD_LOCATION = None
1358
+ settings.GEMINI_MODEL_NAME = None
1359
+ settings.USE_GEMINI_MODEL = None
1360
+
1361
+ handled, path, _ = edit_ctx.result
1362
+
1363
+ if not handled and save is not None:
1364
+ # invalid --save format (unsupported)
1365
+ print("Unsupported --save option. Use --save=dotenv[:path].")
1366
+ elif path:
1367
+ # persisted to a file
1368
+ print(f"Removed Gemini model environment variables from {path}.")
1369
+
1370
+ if is_openai_configured():
1371
+ print(
1372
+ ":raised_hands: OpenAI will still be used by default because OPENAI_API_KEY is set."
1373
+ )
1374
+ else:
1375
+ print(
1376
+ "The Gemini model configuration has been removed. No model is currently configured, but you can set one with the CLI or add credentials to .env[.local]."
1377
+ )
683
1378
 
684
1379
 
685
1380
  @app.command(name="set-litellm")
686
1381
  def set_litellm_model_env(
687
1382
  model_name: str = typer.Argument(..., help="Name of the LiteLLM model"),
688
1383
  api_key: Optional[str] = typer.Option(
689
- None, "--api-key", help="API key for the model (if required)"
1384
+ None,
1385
+ "--api-key",
1386
+ help="API key for the model. Persisted to dotenv if --save is used; never written to the legacy JSON keystore.",
690
1387
  ),
691
1388
  api_base: Optional[str] = typer.Option(
692
1389
  None, "--api-base", help="Base URL for the model API (if required)"
693
1390
  ),
1391
+ save: Optional[str] = typer.Option(
1392
+ None,
1393
+ "--save",
1394
+ help="Persist CLI parameters as environment variables in a dotenv file. "
1395
+ "Usage: --save=dotenv[:path] (default: .env.local)",
1396
+ ),
694
1397
  ):
695
- """Set up a LiteLLM model for evaluation."""
696
- clear_evaluation_model_keys()
697
- KEY_FILE_HANDLER.write_key(ModelKeyValues.LITELLM_MODEL_NAME, model_name)
698
- if api_key:
699
- KEY_FILE_HANDLER.write_key(ModelKeyValues.LITELLM_API_KEY, api_key)
700
- if api_base:
701
- KEY_FILE_HANDLER.write_key(ModelKeyValues.LITELLM_API_BASE, api_base)
702
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_LITELLM, "YES")
703
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_AZURE_OPENAI, "NO")
704
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_LOCAL_MODEL, "NO")
705
- KEY_FILE_HANDLER.write_key(ModelKeyValues.USE_GEMINI_MODEL, "NO")
1398
+ settings = get_settings()
1399
+ with settings.edit(save=save) as edit_ctx:
1400
+ edit_ctx.switch_model_provider(ModelKeyValues.USE_LITELLM)
1401
+ settings.LITELLM_MODEL_NAME = model_name
1402
+ if api_key is not None:
1403
+ settings.LITELLM_API_KEY = api_key
1404
+ if api_base is not None:
1405
+ settings.LITELLM_API_BASE = api_base
1406
+
1407
+ handled, path, _ = edit_ctx.result
1408
+
1409
+ if not handled and save is not None:
1410
+ # invalid --save format (unsupported)
1411
+ print("Unsupported --save option. Use --save=dotenv[:path].")
1412
+ elif path:
1413
+ # persisted to a file
1414
+ print(
1415
+ f"Saved environment variables to {path} (ensure it's git-ignored)."
1416
+ )
1417
+ else:
1418
+ # updated in-memory & process env only
1419
+ print(
1420
+ "Settings updated for this session. To persist, use --save=dotenv[:path] "
1421
+ "(default .env.local) or set DEEPEVAL_DEFAULT_SAVE=dotenv:.env.local"
1422
+ )
1423
+
706
1424
  print(
707
- ":raising_hands: Congratulations! You're now using a LiteLLM model for all evals that require an LLM."
1425
+ f":raising_hands: Congratulations! You're now using LiteLLM's `{escape(model_name)}` for all evals that require an LLM."
708
1426
  )
709
1427
 
710
1428
 
711
1429
  @app.command(name="unset-litellm")
712
- def unset_litellm_model_env():
713
- """Remove LiteLLM model configuration."""
714
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.LITELLM_MODEL_NAME)
715
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.LITELLM_API_KEY)
716
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.LITELLM_API_BASE)
717
- KEY_FILE_HANDLER.remove_key(ModelKeyValues.USE_LITELLM)
718
- print(
719
- ":raising_hands: Congratulations! You're now using regular OpenAI for all evals that require an LLM."
720
- )
1430
+ def unset_litellm_model_env(
1431
+ save: Optional[str] = typer.Option(
1432
+ None,
1433
+ "--save",
1434
+ help="Remove only the LiteLLM related environment variables from a dotenv file. "
1435
+ "Usage: --save=dotenv[:path] (default: .env.local)",
1436
+ ),
1437
+ ):
1438
+ settings = get_settings()
1439
+ with settings.edit(save=save) as edit_ctx:
1440
+ settings.LITELLM_API_KEY = None
1441
+ settings.LITELLM_MODEL_NAME = None
1442
+ settings.LITELLM_API_BASE = None
1443
+ settings.USE_LITELLM = None
1444
+
1445
+ handled, path, _ = edit_ctx.result
1446
+
1447
+ if not handled and save is not None:
1448
+ # invalid --save format (unsupported)
1449
+ print("Unsupported --save option. Use --save=dotenv[:path].")
1450
+ elif path:
1451
+ # persisted to a file
1452
+ print(f"Removed LiteLLM model environment variables from {path}.")
1453
+
1454
+ if is_openai_configured():
1455
+ print(
1456
+ ":raised_hands: OpenAI will still be used by default because OPENAI_API_KEY is set."
1457
+ )
1458
+ else:
1459
+ print(
1460
+ "The LiteLLM model configuration has been removed. No model is currently configured, but you can set one with the CLI or add credentials to .env[.local]."
1461
+ )
721
1462
 
722
1463
 
723
1464
  if __name__ == "__main__":