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

# Pi Agent: Drittanbieter-API, Base URL, API Key und Modelle

> Verbinde Pi Agent über models.json mit einer Drittanbieter-API: Base URL, API Key und Model ID. Wähle das Protokoll, prüfe die Verbindung und behebe Fehler.

export const PiModelPicker = ({locale = "en", plazaUrl = "https://bettertoken.ai/pricing"}) => {
  const translations = {
    en: {
      get: "Get model list",
      refresh: "Refresh model list",
      loading: "Loading models...",
      api: "API",
      search: "Search models",
      models: "Models",
      selected: "Selected",
      select: "Select",
      copyId: "Copy Model ID",
      copyConfig: "Copy configuration",
      download: "Download models.json",
      copied: "Copied",
      copyError: "Copy failed. Select and copy the text below.",
      error: "Could not load models. Try again or open the model plaza.",
      empty: "No matching models.",
      plaza: "Model plaza",
      config: "Pi configuration",
      selectAll: "Select all visible models"
    },
    zh: {
      get: "获取模型列表",
      refresh: "刷新模型列表",
      loading: "正在获取模型...",
      api: "接口",
      search: "搜索模型",
      models: "模型",
      selected: "已选择",
      select: "选择",
      copyId: "复制 Model ID",
      copyConfig: "复制配置",
      download: "下载 models.json",
      copied: "已复制",
      copyError: "复制失败，请选中下方文本复制。",
      error: "暂时无法获取模型，请重试或前往模型广场。",
      empty: "没有匹配的模型。",
      plaza: "模型广场",
      config: "Pi 配置",
      selectAll: "选择当前显示的全部模型"
    },
    ru: {
      get: "Получить список моделей",
      refresh: "Обновить список моделей",
      loading: "Загрузка моделей...",
      api: "API",
      search: "Поиск моделей",
      models: "Модели",
      selected: "Выбрано",
      select: "Выбрать",
      copyId: "Скопировать Model ID",
      copyConfig: "Скопировать настройки",
      download: "Скачать models.json",
      copied: "Скопировано",
      copyError: "Не удалось скопировать. Выделите и скопируйте текст ниже.",
      error: "Не удалось загрузить модели. Повторите попытку или откройте каталог моделей.",
      empty: "Подходящих моделей нет.",
      plaza: "Каталог моделей",
      config: "Настройки Pi",
      selectAll: "Выбрать все показанные модели"
    },
    hi: {
      get: "मॉडल सूची प्राप्त करें",
      refresh: "मॉडल सूची रीफ़्रेश करें",
      loading: "मॉडल लोड हो रहे हैं...",
      api: "API",
      search: "मॉडल खोजें",
      models: "मॉडल",
      selected: "चुने गए",
      select: "चुनें",
      copyId: "Model ID कॉपी करें",
      copyConfig: "कॉन्फ़िगरेशन कॉपी करें",
      download: "models.json डाउनलोड करें",
      copied: "कॉपी हो गया",
      copyError: "कॉपी नहीं हो सका। नीचे दिए गए टेक्स्ट को चुनकर कॉपी करें।",
      error: "मॉडल लोड नहीं हो सके। फिर से प्रयास करें या मॉडल कैटलॉग खोलें।",
      empty: "कोई मेल खाने वाला मॉडल नहीं मिला।",
      plaza: "मॉडल कैटलॉग",
      config: "Pi कॉन्फ़िगरेशन",
      selectAll: "दिखाए गए सभी मॉडल चुनें"
    },
    es: {
      get: "Obtener lista de modelos",
      refresh: "Actualizar lista de modelos",
      loading: "Cargando modelos...",
      api: "API",
      search: "Buscar modelos",
      models: "Modelos",
      selected: "Seleccionados",
      select: "Seleccionar",
      copyId: "Copiar Model ID",
      copyConfig: "Copiar configuración",
      download: "Descargar models.json",
      copied: "Copiado",
      copyError: "No se pudo copiar. Selecciona y copia el texto de abajo.",
      error: "No se pudieron cargar los modelos. Inténtalo de nuevo o abre el catálogo.",
      empty: "No hay modelos coincidentes.",
      plaza: "Catálogo de modelos",
      config: "Configuración de Pi",
      selectAll: "Seleccionar todos los modelos visibles"
    },
    "pt-br": {
      get: "Obter lista de modelos",
      refresh: "Atualizar lista de modelos",
      loading: "Carregando modelos...",
      api: "API",
      search: "Buscar modelos",
      models: "Modelos",
      selected: "Selecionados",
      select: "Selecionar",
      copyId: "Copiar Model ID",
      copyConfig: "Copiar configuração",
      download: "Baixar models.json",
      copied: "Copiado",
      copyError: "Não foi possível copiar. Selecione e copie o texto abaixo.",
      error: "Não foi possível carregar os modelos. Tente novamente ou abra o catálogo.",
      empty: "Nenhum modelo correspondente.",
      plaza: "Catálogo de modelos",
      config: "Configuração do Pi",
      selectAll: "Selecionar todos os modelos visíveis"
    },
    ja: {
      get: "モデル一覧を取得",
      refresh: "モデル一覧を更新",
      loading: "モデルを取得中...",
      api: "API",
      search: "モデルを検索",
      models: "モデル",
      selected: "選択済み",
      select: "選択",
      copyId: "Model ID をコピー",
      copyConfig: "設定をコピー",
      download: "models.json をダウンロード",
      copied: "コピーしました",
      copyError: "コピーできませんでした。下のテキストを選択してコピーしてください。",
      error: "モデルを取得できませんでした。再試行するか、モデルカタログを開いてください。",
      empty: "一致するモデルがありません。",
      plaza: "モデルカタログ",
      config: "Pi の設定",
      selectAll: "表示中のモデルをすべて選択"
    },
    fr: {
      get: "Obtenir la liste des modèles",
      refresh: "Actualiser les modèles",
      loading: "Chargement des modèles...",
      api: "API",
      search: "Rechercher des modèles",
      models: "Modèles",
      selected: "Sélectionnés",
      select: "Sélectionner",
      copyId: "Copier le Model ID",
      copyConfig: "Copier la configuration",
      download: "Télécharger models.json",
      copied: "Copié",
      copyError: "La copie a échoué. Sélectionnez et copiez le texte ci-dessous.",
      error: "Impossible de charger les modèles. Réessayez ou ouvrez le catalogue.",
      empty: "Aucun modèle correspondant.",
      plaza: "Catalogue de modèles",
      config: "Configuration de Pi",
      selectAll: "Sélectionner tous les modèles affichés"
    },
    de: {
      get: "Modellliste abrufen",
      refresh: "Modellliste aktualisieren",
      loading: "Modelle werden geladen...",
      api: "API",
      search: "Modelle suchen",
      models: "Modelle",
      selected: "Ausgewählt",
      select: "Auswählen",
      copyId: "Model ID kopieren",
      copyConfig: "Konfiguration kopieren",
      download: "models.json herunterladen",
      copied: "Kopiert",
      copyError: "Kopieren fehlgeschlagen. Markiere und kopiere den Text unten.",
      error: "Modelle konnten nicht geladen werden. Versuche es erneut oder öffne den Modellkatalog.",
      empty: "Keine passenden Modelle.",
      plaza: "Modellkatalog",
      config: "Pi-Konfiguration",
      selectAll: "Alle angezeigten Modelle auswählen"
    },
    ko: {
      get: "모델 목록 가져오기",
      refresh: "모델 목록 새로고침",
      loading: "모델을 가져오는 중...",
      api: "API",
      search: "모델 검색",
      models: "모델",
      selected: "선택됨",
      select: "선택",
      copyId: "Model ID 복사",
      copyConfig: "설정 복사",
      download: "models.json 다운로드",
      copied: "복사됨",
      copyError: "복사하지 못했습니다. 아래 텍스트를 선택하여 복사하세요.",
      error: "모델을 가져오지 못했습니다. 다시 시도하거나 모델 카탈로그를 여세요.",
      empty: "일치하는 모델이 없습니다.",
      plaza: "모델 카탈로그",
      config: "Pi 설정",
      selectAll: "표시된 모든 모델 선택"
    }
  };
  const t = translations[locale] || translations.en;
  const protocols = {
    "openai-responses": {
      label: "Responses",
      endpoint: "openai-response",
      baseUrl: "https://bettertoken.ai/v1"
    },
    "openai-completions": {
      label: "Chat Completions",
      endpoint: "openai",
      baseUrl: "https://bettertoken.ai/v1"
    },
    "anthropic-messages": {
      label: "Anthropic Messages",
      endpoint: "anthropic",
      baseUrl: "https://bettertoken.ai"
    }
  };
  const [models, setModels] = useState(null);
  const [api, setApi] = useState("openai-completions");
  const [search, setSearch] = useState("");
  const [selected, setSelected] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);
  const [feedback, setFeedback] = useState("");
  const request = useRef(null);
  const protocolGroup = useId();
  useEffect(() => () => {
    request.current?.abort();
    request.current = null;
  }, []);
  const eligible = (models || []).filter(model => model.endpoints.includes(protocols[api].endpoint));
  const visible = eligible.filter(model => model.id.toLowerCase().includes(search.trim().toLowerCase()));
  const chosen = eligible.filter(model => selected.includes(model.id));
  const configuration = JSON.stringify({
    providers: {
      bettertoken: {
        baseUrl: protocols[api].baseUrl,
        api,
        apiKey: "$BETTERTOKEN_API_KEY",
        models: chosen.map(model => ({
          id: model.id
        }))
      }
    }
  }, null, 2);
  const loadModels = async () => {
    if (request.current) return;
    const controller = new AbortController();
    request.current = controller;
    setLoading(true);
    setError(false);
    setFeedback("");
    const timer = setTimeout(() => controller.abort(), 15000);
    try {
      const response = await fetch("https://bettertoken.ai/api/pricing", {
        credentials: "omit",
        cache: "no-store",
        headers: {
          Accept: "application/json"
        },
        signal: controller.signal
      });
      if (!response.ok) throw new Error("Catalog request failed");
      const body = await response.json();
      if (body.success !== true || !Array.isArray(body.data)) throw new Error("Invalid catalog");
      const catalog = new Map();
      for (const entry of body.data) {
        if (typeof entry?.model_name !== "string" || !entry.model_name.trim() || !Array.isArray(entry.supported_endpoint_types)) continue;
        if ((/^(?:nano-banana-|gpt-image-)/i).test(entry.model_name)) continue;
        if (entry.supported_endpoint_types.some(endpoint => ["image-generation", "openai-video", "embeddings", "jina-rerank"].includes(endpoint))) continue;
        const endpoints = entry.supported_endpoint_types.filter(endpoint => Object.values(protocols).some(protocol => protocol.endpoint === endpoint));
        if (!endpoints.length) continue;
        const previous = catalog.get(entry.model_name);
        catalog.set(entry.model_name, {
          id: entry.model_name,
          endpoints: Array.from(new Set([...previous?.endpoints || [], ...endpoints]))
        });
      }
      const next = Array.from(catalog.values()).sort((a, b) => a.id.localeCompare(b.id, "en"));
      if (request.current !== controller) return;
      setModels(next);
      setSelected(current => current.filter(id => next.some(model => model.id === id && model.endpoints.includes(protocols[api].endpoint))));
    } catch {
      if (request.current === controller) setError(true);
    } finally {
      clearTimeout(timer);
      if (request.current === controller) {
        request.current = null;
        setLoading(false);
      }
    }
  };
  const copy = async value => {
    try {
      await navigator.clipboard.writeText(value);
      setFeedback(t.copied);
    } catch {
      setFeedback(t.copyError);
    }
  };
  const download = () => {
    const url = URL.createObjectURL(new Blob([configuration + "\n"], {
      type: "application/json"
    }));
    const link = document.createElement("a");
    link.href = url;
    link.download = "models.json";
    link.click();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  };
  return <div className="bt-pi-models not-prose">
      <div className="bt-pi-models-toolbar">
        <fieldset className="bt-pi-models-protocols" disabled={loading}>
          <legend>{t.api}</legend>
          <div className="bt-pi-models-options">
            {Object.entries(protocols).map(([value, protocol]) => <label key={value}>
                <input type="radio" name={protocolGroup} value={value} checked={api === value} onChange={() => {
    setApi(value);
    setSelected([]);
    setFeedback("");
  }} />
                <span>{protocol.label}</span>
              </label>)}
          </div>
        </fieldset>
        <button type="button" className="bt-pi-models-load" disabled={loading} onClick={loadModels}>
          <Icon icon={models === null ? "list" : "rotate-right"} size={16} />
          <span>{loading ? t.loading : models === null ? t.get : t.refresh}</span>
        </button>
      </div>
      {error && <p role="alert" className="bt-pi-models-error">{t.error} <a href={plazaUrl}>{t.plaza}</a></p>}
      {models !== null && <div>
          <label className="bt-pi-models-field bt-pi-models-search">
            <span>{t.search}</span>
            <input type="search" value={search} onChange={event => setSearch(event.target.value)} />
          </label>
          <p className="bt-pi-models-count" aria-live="polite">{t.models}: {visible.length} / {eligible.length} · {t.selected}: {chosen.length}</p>
          <div className="bt-pi-models-list">
            <table>
              <thead>
                <tr>
                  <th className="bt-pi-models-check">
                    <input type="checkbox" aria-label={t.selectAll} disabled={!visible.length} checked={visible.length > 0 && visible.every(model => selected.includes(model.id))} onChange={event => setSelected(current => event.target.checked ? Array.from(new Set([...current, ...visible.map(model => model.id)])) : current.filter(id => !visible.some(model => model.id === id)))} />
                  </th>
                  <th>Model ID</th>
                  <th className="bt-pi-models-copy" aria-label={t.copyId}></th>
                </tr>
              </thead>
              <tbody>
                {visible.map(model => <tr key={model.id}>
                    <td className="bt-pi-models-check">
                      <input type="checkbox" aria-label={t.select + " " + model.id} checked={selected.includes(model.id)} onChange={event => setSelected(current => event.target.checked ? [...current, model.id] : current.filter(id => id !== model.id))} />
                    </td>
                    <td><code>{model.id}</code></td>
                    <td className="bt-pi-models-copy">
                      <button type="button" className="bt-pi-models-icon" title={t.copyId} aria-label={t.copyId + " " + model.id} onClick={() => copy(model.id)}><Icon icon="copy" size={16} /></button>
                    </td>
                  </tr>)}
              </tbody>
            </table>
            {!visible.length && <p className="bt-pi-models-empty">{t.empty}</p>}
          </div>
          {chosen.length > 0 && <div className="bt-pi-models-config">
              <div className="bt-pi-models-config-header">
                <span>{t.config}</span>
                <div className="bt-pi-models-actions">
                  <button type="button" className="bt-pi-models-icon" title={t.copyConfig} aria-label={t.copyConfig} onClick={() => copy(configuration)}><Icon icon="copy" size={16} /></button>
                  <button type="button" className="bt-pi-models-icon" title={t.download} aria-label={t.download} onClick={download}><Icon icon="download" size={16} /></button>
                </div>
              </div>
              <pre aria-label={t.config}><code>{configuration}</code></pre>
            </div>}
        </div>}
      <p role="status" className="bt-pi-models-feedback">{feedback}</p>
    </div>;
};

Pi Agent, der Terminal-Coding-Agent Pi, verbindet sich über `models.json` mit kompatiblen Drittanbieter-APIs. Das Beispiel nutzt BetterToken: Lege den Schlüssel, die Base URL des Protokolls und die Model ID fest. Für einen anderen Dienst verwende dessen URL, Zugangsdaten und Modell-IDs.

## Wichtige Einstellungen

| Feld | Wert |
| - | - |
| API Key | BetterToken API Key über die Umgebungsvariable `BETTERTOKEN_API_KEY` |
| Base URL | OpenAI-Protokoll: `https://bettertoken.ai/v1`; Anthropic-Protokoll: `https://bettertoken.ai` |
| Model | Die genaue Model ID aus der <a href={"https://bettertoken.ai/pricing"}>model plaza</a> |
| Konfigurationsdatei | `~/.pi/agent/models.json` |

## Voraussetzungen

* Node.js 22.19 oder neuer.
* <a href={"https://bettertoken.ai/register"}>Erstelle einen BetterToken API Key</a> und wähle in der model plaza ein Modell mit Tool-Aufrufen.
* Installiere unter nativem Windows [Git for Windows](https://git-scm.com/downloads/win) für Pis Bash-Tool oder führe Pi in WSL aus.

## Installation

Nutze Pis aktuellen offiziellen npm-Befehl:

```bash theme={null}
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
pi --version
```

## Manuelle Konfiguration

### API Key setzen

Ersetze im Terminal, aus dem du Pi startest, `YOUR_API_KEY` durch deinen Schlüssel:

<Tabs>
  <Tab title="macOS / Linux">
    ```bash theme={null}
    export BETTERTOKEN_API_KEY="YOUR_API_KEY"
    ```
  </Tab>

  <Tab title="Windows PowerShell">
    ```powershell theme={null}
    $env:BETTERTOKEN_API_KEY="YOUR_API_KEY"
    ```
  </Tab>
</Tabs>

### Modelle abrufen und Konfiguration erstellen

Klicke auf **Modellliste abrufen**, um den aktuellen öffentlichen BetterToken-Katalog ohne Eingabe eines API Key zu laden. Wähle eine API und Modelle mit Tool-Aufrufen. Kopiere dann eine Model ID oder lade die erzeugte `models.json` herunter.

Wenn `models.json` bereits existiert, füge den erzeugten Eintrag `bettertoken` in deine Konfiguration ein, statt die Datei zu ersetzen. Die Konfiguration verwendet die oben gesetzte Variable `$BETTERTOKEN_API_KEY`. Ergänze weitere Parameter anhand der tatsächlichen Modellfähigkeiten.

Pi unterstützt mehrere Protokolle. Wähle eines, das Modell und Server unterstützen. Für diese Verbindung empfehlen wir **Anthropic Messages** für Claude, **Responses** für GPT, sofern der Server es unterstützt, und **Chat Completions** für andere OpenAI-kompatible Modelle. Die Auswahl startet mit Chat Completions und filtert nach den im öffentlichen Katalog angegebenen APIs. Fehlt dein Modell unter Responses, nutze das manuelle Beispiel unten für Modelle mit bestätigter Responses-Unterstützung.

<PiModelPicker locale="de" plazaUrl="https://bettertoken.ai/pricing" />

### URL und Modell hinzufügen

Erstelle `~/.pi/agent/models.json`. Unter Windows lautet der Standardpfad `%USERPROFILE%\.pi\agent\models.json`. Falls die Datei existiert, ergänze den Eintrag `bettertoken` im vorhandenen Objekt `providers`.

Alternativ zur erzeugten Konfiguration kannst du dieses Responses-API-Beispiel manuell ausfüllen. Ersetze `YOUR_MODEL_ID` durch eine ID aus der model plaza und behalte `$BETTERTOKEN_API_KEY` als Variablenreferenz bei:

```json theme={null}
{
  "providers": {
    "bettertoken": {
      "baseUrl": "https://bettertoken.ai/v1",
      "api": "openai-responses",
      "apiKey": "$BETTERTOKEN_API_KEY",
      "models": [{ "id": "YOUR_MODEL_ID" }]
    }
  }
}
```

Ändere für eine andere API sowohl `baseUrl` als auch `api` passend:

| API | `api` | `baseUrl` |
| - | - | - |
| Responses API | `openai-responses` | `https://bettertoken.ai/v1` |
| Chat Completions | `openai-completions` | `https://bettertoken.ai/v1` |
| Anthropic Messages | `anthropic-messages` | `https://bettertoken.ai` |

Wähle eine API mit Tool-Aufrufen für dein Modell. GPT-6.1 Sol nutzt beispielsweise Responses. Behalte die angegebene Base URL bei; Pi ergänzt den Anfragepfad.

### Pi starten

Führe im Projektverzeichnis den Befehl mit deiner konfigurierten Model ID aus:

```bash theme={null}
pi --provider bettertoken --model YOUR_MODEL_ID
```

Setze die Umgebungsvariable in einem neuen Terminal erneut oder speichere sie in deiner Shell-Konfiguration.

## Verbindung prüfen

Führe `pi --list-models` aus und prüfe, ob `bettertoken` und dein Modell erscheinen. Starte danach Pi und sende:

```text theme={null}
Reply with PI_OK only. Do not use tools.
```

Teste nach der Antwort `PI_OK` eine kleine schreibgeschützte Aufgabe, um Tool-Aufrufe zu prüfen.

## Modell wechseln

Ergänze Modelle im Array `models` und öffne `/model` in Pi, um `models.json` neu einzulesen. Wähle ein Modell und drücke `Ctrl+S`, um es für neue Sitzungen als Standard zu speichern. Nutze für verschiedene Protokolle getrennte lokale Namen mit eigenen Werten für `api` und `baseUrl`.

## Häufige Fehler

| Symptom | Lösung |
| - | - |
| Modell fehlt in der Liste | Prüfe JSON, Model ID und Umgebungsvariable; öffne `/model` erneut. |
| `401` oder `Unauthorized` | Prüfe den BetterToken API Key in der Variable und starte Pi aus demselben Terminal. |
| `404` oder falsche URL | Gleiche `api` und `baseUrl` mit der Tabelle ab. Entferne manuell angehängte `/responses`, `/chat/completions` oder `/v1/messages`. |
| Text funktioniert, Tools schlagen fehl | Prüfe die Fähigkeiten von Modell und API; nutze bei Modellen, die Responses benötigen, `openai-responses`. |
| Bash fehlt unter Windows | Installiere Git for Windows und öffne ein neues Terminal oder konfiguriere `shellPath` nach Pis Windows-Anleitung. |

## Erweiterte Konfiguration

Setze für Bilder, Reasoning-Stufen oder größeren Kontext `input`, `reasoning`, `contextWindow` und `maxTokens` nach den tatsächlichen Modellfähigkeiten. Siehe den [Leitfaden zur Modellwahl](/de/faq/model-calling/model-selection-guide).

## Technische Details

`providers` und `--provider` sind Konfigurationsfelder des Pi-Clients; `bettertoken` ist der lokale Name in diesem Beispiel. Pi prüft zuerst `--api-key`, dann gespeicherte Zugangsdaten aus `auth.json` und danach `apiKey` in `models.json`. Sind für diesen Namen alte Zugangsdaten gespeichert, entferne den entsprechenden Eintrag mit `/logout` in Pi und verbinde dich erneut.

### Offizielle Pi-Quellen

* [Installation und Schnellstart](https://pi.dev/docs/latest/quickstart)
* [Eigene URLs und Modelle](https://pi.dev/docs/latest/models#configure-a-compatible-endpoint)
* [Befehlszeilenreferenz](https://pi.dev/docs/latest/cli)
* [Windows-Einrichtung](https://pi.dev/docs/latest/windows)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.