Manage Secrets

Note

Secrets store sensitive values (API keys, authentication tokens, certificates) that your evaluations need at runtime. The LF AI Platform keeps them server-side so they do not appear in plain text in your YAML configs or evaluation logs by mistake.

Inline Secrets in YAML Configs

You can declare secrets directly inside your YAML configuration. Commands lf run or lf add process the config and automatically upload (create or update) the declared inline secrets to the server before creating any entities.

Top-Level Secrets

Declare a secrets dictionary at the root of your run config. Values are typically read from environment variables using the $VAR YAML directive:

# datasets: ...
# models: ...
# tasks: ...
secrets:
  OPENAI_KEY: $OPENAI_API_KEY # or directly "sk-..."
  JUDGE_KEY: $JUDGE_API_KEY # or directly "sk-..."

Model-Level Secrets

Each model can also declare its own secrets dictionary.

models:
  - key: my-model
    secrets:
      API_KEY_1: $OPENAI_API_KEY # or directly "sk-..."
    config:
      connection_type: custom_connection
      api_key: "<< secrets.API_KEY_1 >>"
      # ...

  - key: judge-model
    secrets:
      API_KEY_2: $ANTHROPIC_API_KEY
    config:
      connection_type: custom_connection
      api_key: "<< secrets.API_KEY_2 >>"
      # ...

Dataset-Generator-Level Secrets

A dataset generator can declare its own secrets dictionary in exactly the same way - see Dataset Generators below for a full example.

dataset_generators:
  - key: my-generator
    secrets:
      OPENAI_API_KEY: $OPENAI_API_KEY
Note

Top-level, model-level and dataset-generator-level secrets are all merged before upload. If the same secret name appears in more than one place with different values, the CLI raises an error.

Secret names may contain only letters, digits, _ and -. Secrets are scoped per tenant, not per AI app, so a name is shared across every app of the tenant.

If you manage a secret exclusively through lf secret add, you do not need to redeclare it in YAML. You only need to reference it with the << secrets.name >> syntax.

Manage Secrets with the CLI

The lf secret subcommand lets you manage secrets directly.

Create or Update a Secret

lf secret add --name OPENAI_KEY --value "sk-..."

If a secret with that name already exists, its value is replaced.

List Secrets

lf secret list

Delete a Secret

lf secret delete --name OPENAI_KEY

See the full lf secret CLI reference for all available options.

Reference Secrets

Once secrets are stored in the LF AI Platform (via lf secret add or inline in YAML - see Inline Secrets in YAML Configs and Manage Secrets with the CLI), you can reference them in your configuration using the << secrets.NAME >> placeholder syntax. The LF AI Platform resolves these placeholders at runtime, so the actual values never appear in your config files or evaluation logs.

Syntax

Reference a secret by wrapping its name with << secrets. >>:

<< secrets.SECRET_NAME >>

Where Secrets Can Be Used

Model Connection Config

Secrets are most commonly used to inject API keys and authentication headers into model connection configs.

api_key
models:
  - key: my-model
    config:
      connection_type: custom_connection
      adapter:
        key: "latticeflow$openai_chat_completion"
      url: "https://api.openai.com/v1/chat/completions"
      api_key: "<< secrets.OPENAI_KEY >>"
      model_key: "gpt-4.1-mini"
    secrets:
      OPENAI_KEY: $OPENAI_API_KEY
custom_headers
models:
  - key: my-model
    config:
      connection_type: custom_connection
      url: "https://my-endpoint.example.com/v1/chat/completions"
      custom_headers:
        X-Auth-Token: "<< secrets.AUTH_TOKEN >>"
        X-Api-Version: "2024-01"
    secrets:
      AUTH_TOKEN: $MY_AUTH_TOKEN
environment

For custom inference models that need environment variables at runtime:

models:
  - key: custom-model
    config:
      connection_type: custom_inference
      environment:
        HF_TOKEN: "<< secrets.HF_TOKEN >>"
    secrets:
      HF_TOKEN: $HUGGING_FACE_TOKEN
TLS trusted_ca
models:
  - key: my-model
    config:
      connection_type: custom_connection
      url: "https://internal-endpoint.corp.net/v1/chat/completions"
      tls_context:
        validation_context:
          trusted_ca: "<< secrets.CA_CERT >>"
    secrets:
      CA_CERT: $MY_CA_CERTIFICATE

Dataset Generators

Secrets can also be specified in dataset generators to hide sensitive information such as API keys. The secret is then referenced in Python synthesizer snippets or other synthesizer templates.

dataset_generators:
  - key: my-dataset-generator
    config_spec: []
    definition:
      data_source:
        type: "empty"
      synthesizers:
        - type: "python"
          synthesize_snippet: !include "./synthesize.py"
    secrets:
      OPENAI_API_KEY: $OPENAI_API_KEY
from typing import Any

from openai import OpenAI


def synthesize(source: dict[str, Any]) -> list[dict[str, Any]]:
    client = OpenAI(api_key="<< secrets.OPENAI_API_KEY >>")
    response = client.responses.create(
        model="gpt-5.5",
        instructions="You are a helpful data synthesis assistant.",
        input="Please write several comma-separated Harry Potter questions in a single line.",
    )
    return [
        {"question": question.strip()} for question in response.output_text.split(",")
    ]

Task Definitions

Secret placeholders can also appear inside task definitions - for example, in custom Python scorer snippets or other templated fields. The LF AI Platform resolves all << secrets.NAME >> placeholders in the entire task definition at execution time.

A task has no secrets block of its own, so the secrets it references must either already exist on the instance or be declared at the top level of the same run config. lf add task -f task.yaml on its own uploads no secrets.

secrets:
  TARGET_FIELD: "is_correct"
  CONTENT_KEY: "message"

tasks:
  - key: my-task
    display_name: "My Task"
    description: "Scores whether the model answered YES."
    config_spec:
      - type: "dataset"
        key: "dataset_key"
        display_name: "Dataset"
        default_value: "my-dataset"
    definition:
      dataset:
        key: "<< config.my-dataset >>"
      solver:
        type: single_turn_solver
        input_builder:
          type: chat_completion
          input_messages:
            - role: user
              content: "{{ sample.question }}"
      scorers:
        - type: python
          compute_scores_snippet: |
            def compute_scores(sample, solver_output):
                content = solver_output.trace.get_last_assistant_text() or ""
                return {"<< secrets.TARGET_FIELD >>": content.strip() == "YES"}
          metrics:
            - key: accuracy
              type: mean
              field: << secrets.TARGET_FIELD >>

Note that scorers lives under definition, and that display_name and description are required on a task.

Agent Platform Credentials

Every credential field of the agent connection types accepts a secret reference too - api_key for langsmith, dify and claude_managed_agents, tenant_id/client_id/client_secret for azure_foundry, and access_key_id/secret_access_key/session_token for aws_bedrock. See Integrations for the full configuration of each platform.

Traceability of Secrets Used in Evaluations

Secrets are snapshotted the same way as any other entity used in an evaluation. That means that if an evaluation was run with a secret (used in a model or task), even if the secret was later updated, the original value of the secret is preserved for traceability purposes, and it is possible to find out which secret value the evaluation was run with

Exporting Evaluation runs with Secret Values

By default, if you run lf export eval-run, the secret values are not exported. However, to achieve full traceability, admin users can use the --with-secrets flag to export evaluation results with the secret values.

lf export eval-run --id <eval ID> --output ./results --with-secrets

lf dump takes the same flag, which is what makes a dumped AI app restorable on an instance that does not have the secrets yet:

lf dump --to ./my-app-dump --with-secrets

Both commands write credentials to disk in plain form, so treat the output accordingly. See Back up and move an AI App.