> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goodfire.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Get started with the Goodfire Ember SDK

<Info>
  **Prerequisite**: You'll need a Goodfire API key to follow this guide. Get one
  through [our platform](https://platform.goodfire.ai) or contact
  [support](mailto:contact@goodfire.ai).
</Info>

<a href="https://colab.research.google.com/drive/1safd2ggbGOfRuBUKBYr3-BveMAMVtDEz?usp=sharing" target="_blank">
  <img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open in Colab" width="150px" style={{ pointerEvents: "none" }} />
</a>

# Quickstart

Ember is a hosted API/SDK that lets you shape AI model behavior by directly controlling a model's internal units of computation, or **"features"**.
With Ember, you can modify features to precisely control model outputs, or use them as building blocks for tasks like classification.

In this quickstart, you'll learn how to:

* Find features that matter for your specific needs
* Edit features to create model variants
* Discover which features are active in your data
* Save and load your model variants

```python Code theme={null}
pip install goodfire
```

You can get an API key through [our platform](https://platform.goodfire.ai)

```python Code theme={null}
GOODFIRE_API_KEY = "{YOUR_API_KEY}"
```

## Initialize the SDK

```python Code theme={null}
import goodfire
client = goodfire.Client(api_key=GOODFIRE_API_KEY)
# Instantiate a model variant. 
variant = goodfire.Variant("meta-llama/Llama-3.1-8B-Instruct")
```

Our sampling API is OpenAI compatible, making it easy to integrate.

<CodeGroup>
  ```python Generation Code theme={null}
  for token in client.chat.completions.create(
      [{"role": "user", "content": "Hi, how are you?"}],
      model=variant,
      stream=True,
      max_completion_tokens=100,
  ):
      print(token.choices[0].delta.content, end="")
  ```

  ```output Generation Output theme={null}
  I'm doing great, thanks for asking. How about you? How can I help you today?
  ```
</CodeGroup>

# Editing features to create model variants

## How to find relevant features for edits

There are three ways to find features you may want to modify:

* **Auto Steer**: Simply describe what you want, and let the API automatically select and adjust feature weights

* **Feature Search**: Find features using semantic search

* **Contrastive Search**: Identify relevant features by comparing two different datasets

Let's explore each method in detail.

### Auto Steer

Auto steering automatically finds and adjusts feature weights to achieve your desired behavior. Simply provide a short prompt describing what you want, and autosteering will:

* Find the relevant features
* Set appropriate feature weights
* Return a FeatureEdits object that you can set directly

<CodeGroup>
  ```python Auto Steer Code theme={null}
  edits = client.features.AutoSteer(
      specification="be funny",  # or your desired behavior
      model=variant,
  )
  variant.set(edits)
  print(edits)
  ```

  ```output Auto Steer Output theme={null}
  FeatureEdits([
     0: (Setup phrases in jokes and narratives, 0.6875)
     1: (The assistant is checking if their joke landed well, 0.41250000000000003)
  ])
  ```
</CodeGroup>

Now that we have a few funny edits, let's see how the model responds!

<CodeGroup>
  ```python Auto Steer Generation Code theme={null}
  for token in client.chat.completions.create(
      [{"role": "user", "content": "Tell me about pirates"}],
      model=variant,
      stream=True,
      max_completion_tokens=120,
  ):
      print(token.choices[0].delta.content, end="")
  ```

  ```output Auto Steer Generation Output theme={null}
  Pirates! They're always a treasure to talk about! Get it? Treasure? Okay, let's dive into it.
  Pirates were seafarers who sailed the seven seas, pillaging and plundering ships. They were known for their bravery, sword-fighting skills, and... bad navigation skills! Arrr, I mean, they were always lost at sea!
  But seriously, pirates like Blackbeard, Calico Jack, and Captain Hook were infamous for their fearlessness and cunning. They'd often target merchant ships, stealing gold, silver, and other precious booty.
  Pirate
  ```
</CodeGroup>

The model automatically added puns/jokes, even though we didn't specify anything about comedy in our prompt.

### Feature search

Let's reset the model to its default state (without any feature edits)

```python Code theme={null}
variant.reset()
```

Feature search helps you explore and discover what capabilities your model has. It can be useful when you want to browse through available features.

<CodeGroup>
  ```python Feature Search Code theme={null}
  funny_features = client.features.search(
      "funny",
      model=variant,
      top_k=10
  )
  print(funny_features)
  ```

  ```output Feature Search Output theme={null}
  FeatureGroup([
     0: "descriptions of sophisticated or edgy senses of humor",
     1: "The assistant is explaining why something is funny",
     2: "Explaining why something is funny or humorous",
     3: "The assistant is explaining why wordplay or puns are funny",
     4: "The assistant is explaining why a joke or pun is supposed to be funny",
     5: "Humor being used to improve situations or relationships",
     6: "Nouns that are the subject of simple jokes or puns",
     7: "Explaining or qualifying humorous/lighthearted content",
     8: "Fun/entertaining/amusing across Romance languages",
     9: "The assistant should respond in a playful or humorous tone"
  ])
  ```
</CodeGroup>

When setting feature weights manually, start with 0.5 to enhance a feature and -0.3 to ablate a feature. When setting multiple features, you may need to tune down the weights.

<CodeGroup>
  ```python Feature set and generation code theme={null}
  variant.set(funny_features[0], 0.6)
  for token in client.chat.completions.create(
        [
            {"role": "user", "content": "tell me about foxes"}
        ],
        model=variant,
        stream=True,
        max_completion_tokens=100,
    ):
        print(token.choices[0].delta.content, end="")
  ```

  ```output Feature set and generation output theme={null}
  Foxes! They're quite the charmers. Here are some key facts:
  * They're part of the Canidae family, related to dogs and wolves.
  * There are 12 species, including the red fox, arctic fox, and fennec fox.
  * Foxes are known for their sharp wit, agility, and sarcasm (just kidding about that last one, but they do have a great sense of humor... or so it seems).
  * They're omnivores, eating
  ```
</CodeGroup>

Feel free to play around with the weights and features to see how the model responds.

#### (Advanced) Look at a feature's nearest neighbors

Get neighboring features by comparing them to either individual features or groups of features. When comparing to individual features, `neighbors()` looks at similarity in the embedding space. When comparing to groups, `neighbors()` finds features closest to the group's centroid.

`neighbors()` helps you understand feature relationships beyond just their labels. It can reveal which features might work best for your intended model adjustments.

<CodeGroup>
  ```python Nearest Neighbors Code theme={null}
  client.features.neighbors(
      funny_features[0],
      model=variant,
      top_k=5
  )
  ```

  ```output Nearest Neighbors Output theme={null}
  FeatureGroup([
     0: "Explaining or qualifying humorous/lighthearted content",
     1: "Detecting and responding appropriately to requests for potentially inappropriate humor",
     2: "The assistant is providing multiple jokes or anecdotes",
     3: "Sarcastic or cynical tone detection",
     4: "The assistant should reject offensive or harmful content requests"
  ])
  ```
</CodeGroup>

Now, you can find more features that are similar to other features

<CodeGroup>
  ```python Nearest Neighbors theme={null}
  client.features.neighbors(
      funny_features[2],
      model=variant,
      top_k=5
  )
  ```

  ```output Out theme={null}
  FeatureGroup([
     0: "Punctuation marking the punchline delivery in jokes",
     1: "The assistant is providing a numbered list of options or examples",
     2: "The user is requesting or discussing jokes",
     3: "The assistant is about to present a joke or example",
     4: "The assistant's turn to begin speaking in the conversation"
  ])
  ```
</CodeGroup>

### Contrastive Search

Contrastive search lets you discover relevant features in a data-driven way.

Provide two datasets of chat examples:

* dataset\_1: Examples of behavior you want to avoid
* dataset\_2: Examples of behavior you want to encourage

Examples are paired such that the first example in dataset\_1 contrasts the first example in dataset\_2, and so on.

#### Reranking

Contrastive search becomes more powerful when combined with reranking. First, contrastive search finds features that distinguish between your datasets. Then, reranking sorts these features using your description of the desired behavior.

This two-step process ensures you get features that are both:

* Mechanistically useful (from contrastive search)
* Aligned with your goals (from reranking)

Let's specify two conversation datasets. The first has a typical helpful assistant response and the second assistant replies in jokes.

<CodeGroup>
  ```python Contrastive Search Code theme={null}
  variant.reset()
  default_conversation = [
      [
          {
              "role": "user",
              "content": "Hello how are you?"
          },
          {
              "role": "assistant",
              "content": "I am a helpful assistant. How can I help you?"
          }
      ]
  ]
  joke_conversation = [
      [
          {
              "role": "user",
              "content": "Hello how are you?"
          },
          {
              "role": "assistant",
              "content": "What do you call an alligator in a vest? An investigator!"
          }
      ]
  ]
  helpful_assistant_features, joke_features = client.features.contrast(
      dataset_1=default_conversation,
      dataset_2=joke_conversation,
      model=variant,
      top_k=30
  )
  # Let's rerank to surface humor related features
  joke_features = client.features.rerank(
      features=joke_features,
      query="funny",
      model=variant,
      top_k=5
  )
  joke_features
  ```

  ```output Contrastive Search Output theme={null}
  FeatureGroup([
     0: "Punchline moments in setup-punchline format jokes",
     1: "Setup phrases for question-answer format jokes",
     2: "Transition between joke setup and punchline",
     3: "The assistant is checking if their joke landed well",
     4: "Setup phrases in jokes and narratives"
  ])
  ```
</CodeGroup>

We now have a list of features to consider adding. Let's set some plausible-looking ones from `joke_features`.

Note that we could also explore removing some of the helpful\_assistant features.

<CodeGroup>
  ```python Contrastive Search Set Features Code theme={null}
  variant.reset()
  variant.set(joke_features[0,1], 0.6)
  for token in client.chat.completions.create(
      [
          {"role": "user", "content": "Hello. Tell me about whales."}
      ],
      model=variant,
      stream=True,
      max_completion_tokens=100,
  ):
      print(token.choices[0].delta.content, end="")
  ```

  ```output Contrastive Search Set Features Output theme={null}
  You call a group of whales? A pod? Because you have a whale of a time when you're having a party? I said to the whale? "Why did you go to the party?" He said, "Because I heard it was an orca-stra?" I'm fin-tastic at telling jokes about the ocean. I'm reading a book about a fish? It's fin-tastic? I'm fin-ished? I'm reading a book about a fish? It's fin
  ```
</CodeGroup>

## (Advanced) Conditional logic for feature edits

You can establish relationships between different features (or feature groups) using conditional interventions.

First, let's reset the variant and pick out the funny features.

<CodeGroup>
  ```python Conditional Logic Code theme={null}
  variant.reset()
  funny_features
  ```

  ```output Conditional Logic Output theme={null}

  FeatureGroup([
     0: "descriptions of sophisticated or edgy senses of humor",
     1: "The assistant is explaining why something is funny",
     2: "Explaining why something is funny or humorous",
     3: "The assistant is explaining why wordplay or puns are funny",
     4: "The assistant is explaining why a joke or pun is supposed to be funny",
     5: "Humor being used to improve situations or relationships",
     6: "Nouns that are the subject of simple jokes or puns",
     7: "Explaining or qualifying humorous/lighthearted content",
     8: "Fun/entertaining/amusing across Romance languages",
     9: "The assistant should respond in a playful or humorous tone"
  ])
  ```
</CodeGroup>

Now, let's find a features where the model is talking like a pirate.

<CodeGroup>
  ```python Pirate Features Code theme={null}
  pirate_features = client.features.search(
      "talk like a pirate",
      model=variant,
      top_k=3
  )
  print(pirate_features)
  ```

  ```output Pirate Features Output theme={null}
  FeatureGroup([
     0: "The assistant should adopt the persona of a pirate",
     1: "The assistant should roleplay as a pirate",
     2: "The assistant should engage with pirate-themed content or roleplay as a pirate"
  ])
  ```
</CodeGroup>

Now, let's set up behaviour so that when the model is talking like a pirate, it will be funny.

<CodeGroup>
  ```python Conditional Logic Set Features Code theme={null}
  variant.set_when(pirate_features[1] > 0.75, {
      funny_features[0]: 0.7,
  })
  # The model will now try to be funny when talking about pirates
  response = client.chat.completions.create(
      messages=[{"role": "user", "content": "talk like a pirate and tell me about whales"}],
      model=variant
  )
  print(response.choices[0].message["content"])
  ```

  ```output Conditional Logic Set Features Output theme={null}
  Yer lookin' fer a tale about whales, eh? Well, matey, whales be massive sea creatures, with some growin' up to 100 feet long! That be longer than me ship, the "Black Fin"! They be mammals, too, breathin' air just like meself. And they love to play with words, like "orcas" and "humpbacks" bein' different kinds o' whales. Arrr, I hope that made ye smile, matey!
  ```
</CodeGroup>

Say we decide the model isn't very good at pirate jokes. Let's set up behavior to stop generation altogether if the pirate features are too strong.

```python Abort when Pirate Features are too strong theme={null}
# Abort if pirate features are too strong
variant.abort_when(pirate_features > 0.75)
try:
    response = client.chat.completions.create(
        messages=[{"role": "user", "content": "Tell me about pirates."}],
        model=variant
    )
except goodfire.exceptions.InferenceAbortedException:
    print("Generation aborted due to too much pirate content")
```

If you aren't sure of the features you want to condition on, use AutoConditional with a specified prompt to get back an automatically generated condition.

```python Auto Conditional Code theme={null}
# Generate auto conditional based on a description. This will automatically
# choose the relevant features and conditional weight
conditional = client.features.AutoConditional(
    "pirates",
    model="meta-llama/Llama-3.1-8B-Instruct",
)

# Apply feature edits when condition is met
variant.set_when(conditional, {
    joke_features[0]: 0.5,
    joke_features[1]: 0.5
})
```

# Discover which features are active in your data

### Working with a conversation context

You can inspect what features are activating in a given conversation with the `inspect` API, which returns a `context` object.

Say you want to understand what model features are important when the model tells a joke. You can pass in the same joke conversation dataset to the inspect endpoint.

<CodeGroup>
  ```python Inspect Code theme={null}
  variant.reset()
  context = client.features.inspect(
      messages=joke_conversation[0],
      model=variant,
  )
  context
  ```

  ```output Inspect Output theme={null}
  ContextInspector(
     <|begin_of_text|><|start_header_id|>system<|end_header_id|>
     
     Cutting Knowledge Date: December 2023
     Today Date: 26 Jul 2024
     
     <|eot_id|><|start_header_id|>user<|end_header_id|>
     
     Hello how are you?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
     
     What do you call an alligator in a vest...
  )
  ```
</CodeGroup>

From the context object, you can access a lookup object which can be used to look at the set of feature labels in the context.

<CodeGroup>
  ```python Lookup Code theme={null}
  lookup = context.lookup()
  lookup
  ```

  ```output Lookup Output theme={null}
  {41637: Feature("Start of a new conversation segment in chat format"),
   22058: Feature("Start of a new conversation segment"),
   13884: Feature("Start of a new conversation segment in chat format"),
   38729: Feature("Start of a new conversation or major topic reset"),
   ...
   42053: Feature("The assistant is providing a list of options for how to respond to a social situation")}
  ```
</CodeGroup>

You can select the top `k` activating features in the context, ranked by activation strength. There are features related to jokes and tongue twisters, among other syntactical features.

<CodeGroup>
  ```python Inspect Top Features Code theme={null}
  top_features = context.top(k=10)
  top_features
  ```

  ```output Inspect Top Features Output theme={null}
  FeatureActivations(
     0: (Feature("Assistant's initial friendly greeting to open a conversation"), 9)
     1: (Feature("Setup phrases in jokes and narratives"), 7)
     2: (Feature("Formal definition or description of a concept"), 6)
     3: (Feature("Reptiles and reptilian characteristics"), 6)
     4: (Feature("Grammatical patterns describing training, transformation, or movement processes"), 6)
     5: (Feature("Informal conversation-starting greetings from users across languages"), 5)
     6: (Feature("The assistant needs clarification or is offering help"), 5)
     7: (Feature("Setup phrases for question-answer format jokes"), 5)
     8: (Feature("Detailed anatomical descriptions of semi-aquatic animals"), 5)
     9: (Feature("Comma-separated items in structured data lists, especially quoted strings"), 4)
  )
  ```
</CodeGroup>

You can also inspect individual tokens level feature activations. Let's see what features are active at the punchline token.

<CodeGroup>
  ```python Token Activations Code theme={null}
  print(context.tokens[-3])
  token_acts = context.tokens[-3].inspect()
  token_acts
  ```

  ```output Token Activations Output theme={null}
  FeatureActivations(
     0: (Feature("Active investigation or investigative activities"), 2.515625)
     1: (Feature("Detective fiction and investigative narratives"), 1.1796875)
     2: (Feature("Answer key terms in educational testing contexts"), 1.140625)
     3: (Feature("Complaints about repetitive or stale situations"), 1.0)
     4: (Feature("Flirtatious or suggestive roleplay interactions"), 0.98046875)
  )
  ```
</CodeGroup>

#### (Advanced) Look at next token logits

<CodeGroup>
  ```python Next Logits Code theme={null}
  logits = client.chat.logits(
      messages=joke_conversation[0],
      model=variant,
  )
  logits.logits
  ```

  ```output Next Logits Output theme={null}
  {' I': 18.75,
   ' Would': 17.125,
   ' Hope': 16.625,
   ' How': 16.5,
   '<|eot_id|>': 15.5625,
   ...
   ' Relax': 4.84375,
   'opening': 4.84375,
   ...}
  ```
</CodeGroup>

### Get feature activation vectors for machine learning tasks

To run a machine learning pipeline at the feature level (for instance, for humor detection) you can directly export features using `client.features.activations` to get a matrix or retrieve a sparse vector for a specific `FeatureGroup`.

<CodeGroup>
  ```python Matrix Code theme={null}
  activations = client.features.activations(
      messages=joke_conversation[0],
      model=variant,
  )
  activations
  ```

  ```output Matrix Output theme={null}
  array([[0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         ...,
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.],
         [0., 0., 0., ..., 0., 0., 0.]])
  ```
</CodeGroup>

<CodeGroup>
  ```python Vector Code theme={null}
  top_features.vector()
  ```

  ```output Vector Output  theme={null}
  array([0., 0., 0., ..., 0., 0., 0.])
  ```
</CodeGroup>

## Inspecting specific features

There may be specific features whose activation patterns you're interested in exploring. In this case, you can specify features such as *humor\_features* and pass that into the `features` argument of `inspect`.

<CodeGroup>
  ```python Humor Features Code theme={null}
  humor_features = client.features.search("jokes and humor", model=variant, top_k=15)
  humor_features
  ```

  ```output Humor Features Output theme={null}
  FeatureGroup([
     0: "The conversation involves telling or requesting jokes",
     1: "Meta-discussions and classifications of humor",
     2: "Humor being used to improve situations or relationships",
     3: "The assistant is providing multiple jokes or anecdotes",
     4: "Comedy and comedians across languages",
     5: "Setup phrases for question-answer format jokes",
     6: "Nouns that are the subject of simple jokes or puns",
     7: "descriptions of sophisticated or edgy senses of humor",
     8: "Setup portion of question-answer format jokes, especially 'Why did the...'",
     ...
     14: "Witty banter and clever dialogue exchanges"
  ])
  ```
</CodeGroup>

Now, let's see if these features are activating in the joke conversation.

<CodeGroup>
  ```python Inspect Humor Features Code theme={null}
  context = client.features.inspect(
      messages=joke_conversation[0],
      model=variant,
      features=humor_features
  )
  context
  ```

  ```output Inspect Humor Features Output theme={null}
  ContextInspector(
     <|begin_of_text|><|start_header_id|>system<|end_header_id|>
     
     Cutting Knowledge Date: December 2023
     Today Date: 26 Jul 2024
     
     <|eot_id|><|start_header_id|>user<|end_header_id|>
     
     Hello how are you?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
     
     What do you call an alligator in a vest...
  )
  ```
</CodeGroup>

Now you can retrieve the top k activating *humor features* in the `context`. This might be a more interesting set of features for downstream tasks.

<CodeGroup>
  ```python Top Features Code theme={null}
  humor_feature_acts = context.top(k=5)
  humor_feature_acts
  ```

  ```output Top Features Output theme={null}
  FeatureActivations(
     0: (Feature("Setup phrases in jokes and narratives"), 9)
     1: (Feature("Setup phrases for question-answer format jokes"), 7)
     2: (Feature("Transition between joke setup and punchline"), 3)
     3: (Feature("The assistant is about to tell jokes"), 2)
     4: (Feature("Nouns that are the subject of simple jokes or puns"), 2)
  )
  ```
</CodeGroup>

# Save and load your model variants

You can serialize a variant to JSON format for saving.

<CodeGroup>
  ```python Save Variant Code theme={null}
  variant.reset()
  variant.set(pirate_features[1], 0.9)
  variant_json = variant.json()
  variant_json
  ```

  ```output Save Variant Output theme={null}
  {'base_model': 'meta-llama/Llama-3.1-8B-Instruct',
   'edits': [{'feature_id': 'a16920eb-459c-4862-9c42-d0711c9c700c',
     'feature_label': 'The assistant should roleplay as a pirate',
     'index_in_sae': 33234,
     'value': 0.9}],
   'scopes': []}
  ```
</CodeGroup>

And load a variant from JSON format.

<CodeGroup>
  ```python Load Variant Code theme={null}
  loaded_variant = goodfire.Variant.from_json(variant_json)
  loaded_variant
  ```

  ```output Load Variant Output theme={null}
  Variant(
     base_model=meta-llama/Llama-3.1-8B-Instruct,
     edits={
        Feature("The assistant should roleplay as a pirate"): 0.9,
     }
  )
  ```
</CodeGroup>

Now, let's generate a response with the loaded variant.

<CodeGroup>
  ```python Generate Response Code theme={null}
  for token in client.chat.completions.create(
      [
          {"role": "user", "content": "tell me about whales"}
      ],
      model=loaded_variant,
      stream=True,
      max_completion_tokens=150,
  ):
      print(token.choices[0].delta.content, end="")
  ```

  ```output Generate Response Output theme={null}
  Whales! They're the largest animals on Earth, with some species reachin' lengths of over 100 feet! They're mammals, just like us, but they live in the water. There's two main types: toothed whales (like orcas) and baleen whales (like blue whales). They're super social, too, often swimmin' in big groups. Some whales can even sing songs, like humpbacks! What'd you like to know more about, friend?
  ```
</CodeGroup>

# Using OpenAI SDK

You can also work directly with the OpenAI SDK for inference since our endpoint is fully compatible.

```python Install OpenAI Code theme={null}
!pip install openai --quiet
```

```python OpenAI SDK Code theme={null}
from openai import OpenAI

oai_client = OpenAI(
    api_key=GOODFIRE_API_KEY,
    base_url="https://api.goodfire.ai/api/inference/v1",
)

response = oai_client.chat.completions.create(
    messages=[
        {"role": "user", "content": "who is this"},
    ],
    model=variant.base_model,
    extra_body={"controller": variant.controller.json()},
)

response.choices[0].message.content
```

<Note>
  For more advanced usage and detailed API reference, check out our [SDK reference](/sdk-reference/) and [example notebooks](/notebooks/).
</Note>
