# LlamaIndex

LlamaIndex reads `OPENAI_API_KEY` from the environment by default. For a custom
endpoint the parameter is `api_base` (not `base_url`), and the class is
`OpenAILike`.

> **Note:** **Start here if seekrit is new:** [three commands](/docs/guides/frameworks#get-running-in-five-minutes) put the keys in an environment, mint a token bound to it, and export `SEEKRIT_TOKEN`. A token reads [everything in its environment](/docs/guides/frameworks#which-secrets-does-the-agent-get), so there is no per-key or per-framework setup to do before any of the below.

## 1. Wrap the process

```bash
seekrit run -- python query.py
seekrit run -- uvicorn app:app
```

```python
from llama_index.core import Settings
from llama_index.llms.openai import OpenAI

Settings.llm = OpenAI(model="gpt-5.6-terra")
```

A RAG app usually needs more than the model key — a vector store URL, a database
password, an embedding provider. One `seekrit run` covers all of them; they are
the same environment.

## 2. Resolve in code

```python
import seekrit
from llama_index.core import Settings
from llama_index.llms.openai import OpenAI

secrets = seekrit.Client().resolve()

Settings.llm = OpenAI(model="gpt-5.6-terra", api_key=secrets["OPENAI_API_KEY"])
```

In a notebook, use the one-call form instead — it takes the token from a password
prompt when there isn't one in the environment, so it never gets saved into the
`.ipynb`, and it returns names rather than values so a displayed cell writes a
summary and nothing more:

```python
import seekrit

seekrit.load()
```

## 3. Never hold the key

```python
from llama_index.llms.openai_like import OpenAILike

Settings.llm = OpenAILike(
    model="gpt-5.6-terra",
    api_base="http://127.0.0.1:8080/openai/v1",
    api_key="{{seekrit:OPENAI_API_KEY}}",
    is_chat_model=True,
)
```

```toml
# seekrit-proxy.toml
listen = "127.0.0.1:8080"

[[route]]
prefix = "/openai"
upstream = "https://api.openai.com"
allow = ["OPENAI_API_KEY"]
methods = ["POST"]
paths = ["/v1/chat/completions", "/v1/embeddings"]
```

`OpenAILike` exists because the first-party `OpenAI` class assumes OpenAI's own
capabilities; tell it `is_chat_model=True` and, if you use tool calling,
`is_function_calling_model=True`.

### Without running the proxy

Both `OpenAI` and `OpenAILike` take an `http_client`:

```python
import httpx
from llama_index.llms.openai import OpenAI
from seekrit.transport import SeekritTransport

Settings.llm = OpenAI(
    model="gpt-5.6-terra",
    api_key="{{seekrit:OPENAI_API_KEY}}",
    http_client=httpx.Client(
        transport=SeekritTransport(allow={"api.openai.com": ["OPENAI_API_KEY"]}),
    ),
)
```

Weaker than the proxy, since it runs in your process:
[in-process injection](/docs/guides/agent-proxy/in-process) sets out the
trade-off. Remember `Settings.embed_model` needs the same treatment.

## Gotchas

- **Embeddings are a second route.** `Settings.embed_model` builds its own
  client, so pointing only `Settings.llm` at the proxy leaves embedding calls
  going direct. Set both, and keep `/v1/embeddings` in the route's `paths`.
- **Indexes get built once and read forever.** A vector store credential often
  outlives the process that used it. That is a rotation question, not an
  injection one — see [secret rotation](/docs/concepts/rotation).
- **A notebook is the easiest place to leak a value.** `print(secrets)` in a cell
  writes plaintext into a file people commit. `seekrit.load()` exists to make the
  safe thing the short thing.
