<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[DaoXE Notes]]></title><description><![CDATA[DaoXE Notes]]></description><link>https://daoxe-notes.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>DaoXE Notes</title><link>https://daoxe-notes.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 10 Sep 2026 07:50:39 GMT</lastBuildDate><atom:link href="https://daoxe-notes.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA["OpenAI-compatible" is a spectrum, not a boolean — a conformance checklist]]></title><description><![CDATA[Two endpoints both say "OpenAI-compatible" on the tin. You point your app at the first one and everything works. You point it at the second one and everything works too — for about a week. Then a stre]]></description><link>https://daoxe-notes.hashnode.dev/openai-compatible-is-a-spectrum-not-a-boolean-a-conformance-checklist</link><guid isPermaLink="true">https://daoxe-notes.hashnode.dev/openai-compatible-is-a-spectrum-not-a-boolean-a-conformance-checklist</guid><category><![CDATA[AI]]></category><category><![CDATA[Python]]></category><category><![CDATA[api]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Seven]]></dc:creator><pubDate>Mon, 07 Sep 2026 20:50:31 GMT</pubDate><content:encoded><![CDATA[<p>Two endpoints both say "OpenAI-compatible" on the tin. You point your app at the first one and everything works. You point it at the second one and everything works too — for about a week. Then a streamed tool call comes back with <code>arguments</code> split across chunks in a way your accumulator didn't expect, your JSON parser throws inside a retry loop, and the retry loop hammers the endpoint because the error body doesn't have the field your backoff code reads.</p>
<p>Nothing lied to you. "OpenAI-compatible" was never a boolean. It's a surface area, and every implementation covers a different subset of it.</p>
<p>I maintain a gateway, so I read a lot of compatibility bug reports. <strong>Disclosure: I work on</strong> <a href="https://daoxe.com/?utm_source=devto&amp;utm_medium=organic&amp;utm_campaign=en_launch&amp;utm_term=en"><strong>daoxe</strong></a><strong>, a multi-model gateway that speaks the OpenAI protocol among others.</strong> The checklist below is deliberately written so you can run it against us and against anyone else, and the script at the end doesn't know or care which endpoint you point it at. If it makes us look bad on a check, that's the correct output.</p>
<h3>The nine surfaces</h3>
<p><strong>1. Streaming deltas.</strong> The reference behaviour is specific: the first <code>chat.completion.chunk</code> carries <code>choices[0].delta.role = "assistant"</code> and usually empty content, middle chunks carry <code>delta.content</code> fragments, the last content-bearing chunk carries <code>finish_reason</code>, and the stream terminates with a literal <code>data: [DONE]</code> line. Implementations diverge on all four. Some never send the role chunk, which breaks clients that use it to open a message. Some put <code>finish_reason</code> on a trailing chunk with an empty <code>delta</code>. Some omit <code>[DONE]</code> entirely and just close the connection, which is fine for a client that reads to EOF and fatal for one that blocks waiting for the sentinel.</p>
<p><strong>2. Tool / function calling.</strong> Two traps here. First, <code>function.arguments</code> is a <strong>JSON-encoded string</strong>, not an object — endpoints that "helpfully" return a parsed object break every client that calls <code>json.loads</code> on it. Second, in streaming mode, tool calls arrive as fragments that you reassemble by the <code>index</code> field, with <code>id</code> and <code>function.name</code> typically only present on the first fragment. An endpoint that re-sends <code>id</code> on every fragment, or that omits <code>index</code> when there's only one call, will work with your naive accumulator and fail the moment a model emits two parallel calls. Also check that <code>finish_reason</code> is <code>tool_calls</code> and not <code>stop</code> — agent loops branch on that value.</p>
<p><strong>3.</strong> <code>response_format</code><strong>.</strong> Three tiers, and they're commonly conflated: no support, <code>{"type": "json_object"}</code> (valid JSON, any shape), and <code>{"type": "json_schema", "json_schema": {..., "strict": true}}</code> (constrained decoding against your schema). The dangerous middle case is an endpoint that <strong>accepts the parameter and ignores it</strong>. You get prose with a code fence around it, your parser fails one request in fifty, and it looks like a model quality problem.</p>
<p><strong>4.</strong> <code>logprobs</code> <strong>/</strong> <code>top_logprobs</code><strong>.</strong> Usually the first thing a proxy layer drops, because almost nobody notices. If you do classification by comparing token probabilities, or you use logprobs for confidence gating, this is load-bearing and you should test it explicitly.</p>
<p><strong>5.</strong> <code>temperature</code> <strong>and sampling params.</strong> Reasoning-style models reject <code>temperature</code> outright on some upstreams, accept-and-ignore it on others, and honour it on a third set. All three are defensible; not knowing which one you're on is not. The same applies to <code>top_p</code>, <code>presence_penalty</code>, and <code>max_tokens</code> vs <code>max_completion_tokens</code>.</p>
<p><strong>6.</strong> <code>stop</code> <strong>sequences.</strong> Does the endpoint honour an array of stop strings? Is the stop sequence included in or excluded from the returned content (the reference excludes it)? Does <code>finish_reason</code> come back as <code>"stop"</code>? A surprising number of shims implement <code>stop</code> by post-truncating the full completion, which means you pay for tokens you never see — and the usage numbers will show it.</p>
<p><strong>7. Usage accounting.</strong> Non-streaming responses should carry <code>usage.prompt_tokens</code>, <code>completion_tokens</code>, <code>total_tokens</code>. Streaming responses only include usage if you pass <code>stream_options: {"include_usage": true}</code>, and then it arrives in a final chunk with an <strong>empty</strong> <code>choices</code> array — a shape that crashes clients which assume <code>choices[0]</code> always exists. If you do cost attribution per request, also check whether cached-prompt and reasoning-token breakdowns survive.</p>
<p><strong>8. Error-body shape.</strong> Every retry layer you've ever written parses this, and nobody tests it. The reference is <code>{"error": {"message": ..., "type": ..., "param": ..., "code": ...}}</code> with an HTTP status that matches the semantics. Real-world variations: a 200 with an error object in the body (fatal for retry logic — you'll cheerfully return an error string to your user), an HTML error page from an intermediate proxy, or a 500 where a 400 belonged, which turns a permanent client error into an infinite retry storm.</p>
<p><strong>9.</strong> <code>/v1/models</code> <strong>fidelity.</strong> Does it exist, does it return <code>{"object": "list", "data": [{"id": ...}]}</code>, and — the part that matters — does it list the IDs your key can actually call? A catalogue endpoint that returns everything the vendor sells, rather than everything your key is entitled to, is worse than no catalogue at all, because you'll build CI checks on top of it.</p>
<h3>The script</h3>
<p>Stdlib only, Python 3.8+, no install. It runs eleven checks and prints a verdict table.</p>
<pre><code class="language-python">#!/usr/bin/env python3
"""compat_probe.py — how OpenAI-compatible is this endpoint, really?

    export LLM_BASE_URL=https://api.example.com/v1
    export LLM_API_KEY=sk-...
    python3 compat_probe.py --model &lt;exact-model-id&gt;
"""
import argparse, json, os, sys, urllib.error, urllib.request

BASE = os.environ.get("LLM_BASE_URL", "").rstrip("/")
KEY = os.environ.get("LLM_API_KEY", "")
OUT = []


def _open(path, payload=None, method="GET", timeout=90):
    data = json.dumps(payload).encode() if payload is not None else None
    req = urllib.request.Request(BASE + path, data=data, method=method)
    req.add_header("Authorization", "Bearer " + KEY)
    if data is not None:
        req.add_header("Content-Type", "application/json")
    try:
        return urllib.request.urlopen(req, timeout=timeout)
    except urllib.error.HTTPError as exc:   # still a readable file object
        return exc


def call(path, payload=None, method="GET"):
    resp = _open(path, payload, method)
    raw = resp.read().decode("utf-8", "replace")
    try:
        return resp.getcode(), json.loads(raw)
    except ValueError:
        return resp.getcode(), raw


def stream(payload):
    payload = dict(payload, stream=True)
    resp = _open("/chat/completions", payload, "POST")
    chunks, saw_done = [], False
    for line in resp:
        line = line.decode("utf-8", "replace").strip()
        if not line.startswith("data:"):
            continue
        body = line[5:].strip()
        if body == "[DONE]":
            saw_done = True
            break
        try:
            chunks.append(json.loads(body))
        except ValueError:
            pass
    return chunks, saw_done


def record(name, ok, detail):
    OUT.append((name, "PASS" if ok is True else "FAIL" if ok is False else "PARTIAL", detail))


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--model", required=True)
    model = ap.parse_args().model
    ask = {"model": model, "messages": [{"role": "user", "content": "Say hi."}],
           "max_tokens": 24}

    # 1 — catalogue
    code, body = call("/models")
    ids = [m.get("id") for m in body.get("data", [])] if isinstance(body, dict) else []
    record("models_endpoint", code == 200 and bool(ids),
           f"HTTP {code}, {len(ids)} ids, target listed: {model in ids}")

    # 2 — basic completion + model echo + usage
    code, body = call("/chat/completions", ask, "POST")
    msg = (body.get("choices") or [{}])[0].get("message", {}) if isinstance(body, dict) else {}
    usage = body.get("usage") if isinstance(body, dict) else None
    record("basic_chat", bool(msg.get("content")), f"HTTP {code}")
    record("model_echo", isinstance(body, dict) and body.get("model") == model,
           f"asked {model!r}, got {body.get('model')!r}" if isinstance(body, dict) else "n/a")
    record("usage_fields", bool(usage and usage.get("total_tokens") is not None), str(usage))

    # 3 — streaming shape
    chunks, done = stream(ask)
    role = any((c.get("choices") or [{}])[0].get("delta", {}).get("role") for c in chunks)
    fin = any((c.get("choices") or [{}])[0].get("finish_reason") for c in chunks)
    record("stream_shape", bool(chunks) and role and fin and done,
           f"{len(chunks)} chunks, role_chunk={role}, finish_reason={fin}, [DONE]={done}")

    # 4 — usage on stream
    chunks, _ = stream(dict(ask, stream_options={"include_usage": True}))
    record("stream_usage", any(c.get("usage") for c in chunks),
           "final usage chunk present" if any(c.get("usage") for c in chunks) else "absent")

    # 5 — tool calling
    tool = {"type": "function", "function": {"name": "get_weather", "parameters": {
        "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}}
    code, body = call("/chat/completions", dict(
        ask, messages=[{"role": "user", "content": "Weather in Osaka? Use the tool."}],
        tools=[tool], tool_choice="auto"), "POST")
    choice = (body.get("choices") or [{}])[0] if isinstance(body, dict) else {}
    calls = choice.get("message", {}).get("tool_calls") or []
    args_is_str = bool(calls) and isinstance(calls[0].get("function", {}).get("arguments"), str)
    record("tool_calls", bool(calls) and args_is_str and choice.get("finish_reason") == "tool_calls",
           f"n={len(calls)} arguments_is_string={args_is_str} finish={choice.get('finish_reason')}")

    # 6 — json mode
    code, body = call("/chat/completions", dict(
        ask, messages=[{"role": "user", "content": "Return {\"ok\": true} as JSON."}],
        response_format={"type": "json_object"}), "POST")
    text = ((body.get("choices") or [{}])[0].get("message", {}).get("content")
            if isinstance(body, dict) else "") or ""
    try:
        json.loads(text); parsed = True
    except ValueError:
        parsed = False
    record("json_object", code == 200 and parsed, f"HTTP {code}, parses={parsed}")

    # 7 — strict schema
    schema = {"type": "json_schema", "json_schema": {"name": "r", "strict": True, "schema": {
        "type": "object", "properties": {"ok": {"type": "boolean"}},
        "required": ["ok"], "additionalProperties": False}}}
    code, _ = call("/chat/completions", dict(ask, response_format=schema), "POST")
    record("json_schema_strict", code == 200, f"HTTP {code}")

    # 8 — logprobs
    code, body = call("/chat/completions", dict(ask, logprobs=True, top_logprobs=3), "POST")
    lp = ((body.get("choices") or [{}])[0].get("logprobs") if isinstance(body, dict) else None)
    record("logprobs", bool(lp and lp.get("content")), f"HTTP {code}")

    # 9 — temperature: accepted / rejected / ignored is three different worlds
    code, _ = call("/chat/completions", dict(ask, temperature=0.5), "POST")
    record("temperature", None if code == 400 else code == 200,
           "rejected with 400 (reasoning model?)" if code == 400 else f"HTTP {code}")

    # 10 — stop sequences
    code, body = call("/chat/completions", dict(
        ask, messages=[{"role": "user", "content": "Count: one two three four five"}],
        stop=["three"], max_tokens=48), "POST")
    text = ((body.get("choices") or [{}])[0].get("message", {}).get("content")
            if isinstance(body, dict) else "") or ""
    record("stop_sequences", "three" not in text, f"stop string leaked into content: {'three' in text}")

    # 11 — error shape on a model that cannot exist
    code, body = call("/chat/completions", dict(ask, model="definitely-not-a-model-xyz"), "POST")
    shaped = isinstance(body, dict) and isinstance(body.get("error"), dict)
    record("error_shape", 400 &lt;= code &lt; 500 and shaped, f"HTTP {code}, error object: {shaped}")

    width = max(len(n) for n, _, _ in OUT)
    for name, verdict, detail in OUT:
        print(f"{name.ljust(width)}  {verdict:&lt;7} {detail}")
    print("\nPARTIAL is not a failure — it is a behaviour you now have to design around.")
    return 0 if all(v != "FAIL" for _, v, _ in OUT) else 1


if __name__ == "__main__":
    sys.exit(main())
</code></pre>
<p>Two notes on running it. It costs a handful of tiny completions, so run it against a cheap model first to confirm your base URL and key are right. And run it against <strong>the exact model ID you plan to ship</strong>, because conformance varies per model on the same endpoint — reasoning models in particular reject parameters that their non-reasoning siblings accept.</p>
<h3>Reading the output</h3>
<p><code>FAIL</code> on <code>basic_chat</code> or <code>error_shape</code> is a real problem. <code>FAIL</code> on <code>logprobs</code> is only a problem if you use logprobs. The point of the table isn't a score; it's that you now have a written record of which surfaces you're allowed to depend on, which you can re-run after any provider change and diff.</p>
<p>Three habits that follow from it:</p>
<ul>
<li><p><strong>Check it into CI.</strong> One nightly run, output committed as an artifact. Compatibility regressions are silent by nature — nobody sends a changelog entry saying "we stopped forwarding <code>logprobs</code>".</p>
</li>
<li><p><strong>Assert the model echo in production</strong>, not just in the probe. If you asked for one ID and the response's <code>model</code> field says another, you want that in your logs the day it starts happening, not the week you notice quality dropped.</p>
</li>
<li><p><strong>Treat</strong> <code>PARTIAL</code> <strong>as a design input.</strong> If <code>stream_usage</code> is absent, your cost attribution needs a non-streaming path or a local tokenizer estimate. That's a two-hour decision now or a month-end reconciliation mystery later.</p>
</li>
</ul>
<h3>What this can't tell you</h3>
<p>The probe tests the <strong>protocol</strong>, not the model behind it. An endpoint can pass all eleven checks and still be routing you to a smaller model, a quantized build, or a truncated context window — the wire format would be perfect either way. That's a different investigation with different tools, and the honest framing there is that behavioural probes give you <em>signals, not proof</em>: nobody outside the provider can see which weights served your request.</p>
<p>But protocol conformance is the part you can settle in an afternoon, with a script that fits on one screen, and it's the part that will wake you up at 3am if you skip it.</p>
]]></content:encoded></item><item><title><![CDATA[LobeChat в Docker + один ключ к нескольким моделям: как это собрать и что видно в логах]]></title><description><![CDATA[LobeChat в Docker + один ключ к нескольким моделям: как это собрать и что реально видно в логах
Руководство на русском: поднять собственный чат-клиент за пять команд и подключить к нему несколько LLM ]]></description><link>https://daoxe-notes.hashnode.dev/lobechat-docker</link><guid isPermaLink="true">https://daoxe-notes.hashnode.dev/lobechat-docker</guid><dc:creator><![CDATA[Seven]]></dc:creator><pubDate>Fri, 04 Sep 2026 15:15:04 GMT</pubDate><content:encoded><![CDATA[<h1>LobeChat в Docker + один ключ к нескольким моделям: как это собрать и что реально видно в логах</h1>
<p><em>Руководство на русском: поднять собственный чат-клиент за пять команд и подключить к нему несколько LLM через один endpoint. Дисклеймер: мы ведём DaoXE, к которому ведёт часть конфигурации, — но сам клиент вы держите у себя, и это как раз цель статьи.</em></p>
<h2>Почему «свой клиент» ≠ «полностью локальные модели»</h2>
<p>Честно разделим два разных желания:</p>
<ul>
<li><p><strong>Локальные модели</strong> (Ollama, GGUF) — данные и вычисления не покидают вашу машину. Ценой: качество ниже фронтирных моделей, нужны GPU, а «приватность» достигается только тем, что запрос никуда не идёт.</p>
</li>
<li><p><strong>Свой клиент + внешний API</strong> — интерфейс, история чатов и ключи у вас; генерация идёт к провайдеру модели. Приватность — это контроль над тем, <strong>куда</strong> и <strong>что</strong> уходит, а не «ничего никуда не уходит».</p>
</li>
</ul>
<p>Эта статья про второй случай: поднять LobeChat в Docker и направить его на единый OpenAI-совместимый endpoint. Смысл этого пути — не держать десять разных клиентов и одиннадцать аккаунтов, сохранив при этом контроль над собственной частью стека.</p>
<h2>Шаг 1. Поднять LobeChat в Docker</h2>
<pre><code class="language-yaml"># docker-compose.yml
services:
  lobechat:
    image: lobehub/lobe-chat
    ports: ["3210:3210"]
    restart: unless-stopped
    environment:
      - NEXT_AUTH_SSO_PROVIDERS=
</code></pre>
<pre><code class="language-bash">docker compose up -d
</code></pre>
<p>Откройте <code>http://localhost:3210</code>. Клиент локальный; ключи хранятся в вашем браузере/базе, а не у третьих сторон.</p>
<h2>Шаг 2. Подключить несколько моделей через один ключ</h2>
<p>В настройках провайдера LobeChat:</p>
<ul>
<li><p><strong>OpenAI</strong>: Base URL <code>https://api.daoxe.com/v1</code>, ваш ключ → модели из <code>GET /v1/models</code>.</p>
</li>
<li><p><strong>Anthropic</strong>: отдельно можно прописать нативный <code>/v1/messages</code> для Claude.</p>
</li>
</ul>
<p>Один ключ, один баланс, много моделей — без отдельной подписки под каждую.</p>
<h2>Шаг 3. Что видно в логах (и зачем это нужно)</h2>
<p>Главное преимущество своего клиента — <strong>наблюдаемость</strong>. Включите логирование запросов и смотрите:</p>
<ul>
<li><p><code>model</code> <strong>в ответе</strong> — та ли модель реально ответила (см. нашу статью про верификацию подмены);</p>
</li>
<li><p><code>usage</code> — сколько токенов ушло; по нему сверяете биллинг endpoint'а. Расхождение больше, чем объясняет тариф, — это вопрос к поставщику;</p>
</li>
<li><p><strong>куда уходит запрос</strong> — URL вы задаёте сами, и видите его же в логах.</p>
</li>
</ul>
<pre><code class="language-bash"># быстрый smoke-тест через ваш клиент-эндпоинт
curl -s https://api.daoxe.com/v1/chat/completions \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"model":"ТОЧНЫЙ_ID","max_tokens":16,"messages":[{"role":"user","content":"ping"}]}' \
  | jq '{model, usage}'
</code></pre>
<h2>Шаг 4. Модельный выбор: как не ошибиться</h2>
<ul>
<li><p>Нужен фронтир (сложный код, длинные контексты) → топовые Claude/GPT через endpoint.</p>
</li>
<li><p>Дешёвые массовые задачи (классификация, черновики) → открытые модели тем же ключом.</p>
</li>
<li><p>Чувствительные данные → держите отдельный локальный Ollama для того, что не должно уходить наружу, и не путайте его с облачным путём.</p>
</li>
</ul>
<p>Гибрид «чувствительное — локально, остальное — облако через один ключ» — самый частый рабочий паттерн.</p>
<h2>Цены и оплата</h2>
<p>Способы оплаты и актуальные цены по каждой модели — на <a href="https://daoxe.com/pricing?utm_source=hashnode&amp;utm_medium=organic&amp;utm_campaign=ru_test_0904&amp;utm_content=hn_t5">daoxe.com/pricing</a>; тарификация по моделям — в USD.</p>
<h2>Дальше</h2>
<p>Гид по десяткам клиентов: <a href="https://seven7763.github.io/daoxe-guide/ru/?utm_source=hashnode&amp;utm_medium=organic&amp;utm_campaign=ru_test_0904&amp;utm_content=hn_t5">seven7763.github.io/daoxe-guide/ru</a>. Вопросы — Telegram @daoxe_ai.</p>
<p><em>Мы не обещаем «100% стабильности» и не утверждаем, что умеем доказывать происхождение токена. Мы утверждаем меньше: всё перечисленное выше можно проверить у себя в логах.</em></p>
]]></content:encoded></item><item><title><![CDATA[Один ключ — несколько моделей: подключаем Cursor, Cline и Claude Code к единому endpoint]]></title><description><![CDATA[Один ключ — несколько моделей: подключаем Cursor, Cline и Claude Code к единому endpoint
Полное руководство на русском: как свести Claude, GPT, Gemini и DeepSeek к одному API-ключу и настроить три поп]]></description><link>https://daoxe-notes.hashnode.dev/cursor-cline-claude-code-endpoint</link><guid isPermaLink="true">https://daoxe-notes.hashnode.dev/cursor-cline-claude-code-endpoint</guid><dc:creator><![CDATA[Seven]]></dc:creator><pubDate>Fri, 04 Sep 2026 15:08:09 GMT</pubDate><content:encoded><![CDATA[<h1>Один ключ — несколько моделей: подключаем Cursor, Cline и Claude Code к единому endpoint</h1>
<p><em>Полное руководство на русском: как свести Claude, GPT, Gemini и DeepSeek к одному API-ключу и настроить три популярных клиента за пять минут. Дисклеймер: мы ведём сервис DaoXE, о котором идёт речь, — все шаги ниже проверяемы и работают с любым OpenAI-совместимым endpoint'ом.</em></p>
<h2>Зачем это нужно</h2>
<p>Типичный набор разработчика в 2026-м: подписка на ChatGPT, доступ к Claude, Gemini в отдельном аккаунте, плюс пара открытых моделей для дешёвых задач. Итог — несколько аккаунтов, несколько биллингов, и каждый раз при смене модели нужно менять клиент или ключ.</p>
<p>Единый OpenAI-совместимый шлюз решает это одной точкой входа: один <code>base_url</code>, один ключ, один баланс — а моделей много. Разберём на трёх популярных клиентах.</p>
<h2>Три протокола, которые должны быть у шлюза</h2>
<ol>
<li><p><strong>OpenAI Chat Completions</strong> (<code>/v1/chat/completions</code>) — говорит почти весь существующий инструментарий.</p>
</li>
<li><p><strong>OpenAI Responses</strong> (<code>/v1/responses</code>) — новый формат OpenAI, его начинают ждать клиенты.</p>
</li>
<li><p><strong>Anthropic Messages</strong> (<code>/v1/messages</code>) — нативный протокол Claude. Без него Claude Code подключается через OpenAI-обёртку, и часть фич (tool use, cache_control, thinking) ведёт себя иначе.</p>
</li>
</ol>
<p>Проверить наличие третьего просто: прямой запрос к <code>/v1/messages</code> с заголовком <code>x-api-key</code> должен отвечать, а не возвращать 404.</p>
<h2>Шаг 1. Ключ и список моделей</h2>
<pre><code class="language-bash">export DAOXE_API_KEY="ваш_ключ"
curl -s https://api.daoxe.com/v1/models \
  -H "Authorization: Bearer ${DAOXE_API_KEY}" | jq -r '.data[].id' | sort
</code></pre>
<p>Список аккаунт-скоупд: видите именно то, что доступно вам. Берите <strong>точные ID</strong> — «примерное название» не работает ни у кого.</p>
<h2>Шаг 2. Минимальный запрос</h2>
<pre><code class="language-bash">curl -s https://api.daoxe.com/v1/chat/completions \
  -H "Authorization: Bearer ${DAOXE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"model":"ТОЧНЫЙ_ID_МОДЕЛИ","max_tokens":16,
       "messages":[{"role":"user","content":"ping"}]}'
</code></pre>
<p>Ответ содержит поле <code>usage</code> — по нему сверяете биллинг (об этом ниже).</p>
<h2>Шаг 3. Cursor</h2>
<p>Settings → Models → включить <strong>OpenAI API Key</strong>:</p>
<ul>
<li><p><strong>Override OpenAI Base URL</strong>: <code>https://api.daoxe.com/v1</code></p>
</li>
<li><p>API Key: ваш ключ</p>
</li>
<li><p>Добавить точные ID моделей в список.</p>
</li>
</ul>
<h2>Шаг 4. Cline / Roo Code</h2>
<p>Провайдер <strong>OpenAI Compatible</strong>, Base URL <code>https://api.daoxe.com/v1</code>, ключ + точный ID модели.</p>
<h2>Шаг 5. Claude Code — нативно</h2>
<pre><code class="language-bash">export ANTHROPIC_BASE_URL="https://api.daoxe.com"   # корень хоста, клиент сам добавит /v1/messages
export ANTHROPIC_AUTH_TOKEN="ваш_ключ"
</code></pre>
<p>После этого Claude Code работает штатно: tool use, стриминг, длинные сессии — без прослойки-роутера.</p>
<h2>Частые грабли</h2>
<ul>
<li><p><strong>Неверный ID модели</strong> — самая частая причина 404. Сверяйтесь с <code>GET /v1/models</code>, а не с чужими статьями: списки меняются.</p>
</li>
<li><p><strong>Перепутаны endpoint'ы</strong>: <code>/v1/chat/completions</code> и <code>/v1/messages</code> — разные протоколы, ключ один.</p>
</li>
<li><p><strong>Таймауты на длинных задачах</strong> — для них есть отдельная прямая линия <code>jp.daoxe.com</code>; рекомендуемый основной endpoint — <code>api.daoxe.com</code> (см. также <code>daoxe.com/v1</code>).</p>
</li>
<li><p><strong>Биллинг</strong>: сверяйте списания с полем <code>usage</code> в ответах. Расхождение больше, чем объясняет тариф, — вопрос к поставщику, и это правильный вопрос.</p>
</li>
</ul>
<h2>Оплата и цены</h2>
<p>Способы оплаты и актуальные цены по каждой модели — на <a href="https://daoxe.com/pricing?utm_source=hashnode&amp;utm_medium=organic&amp;utm_campaign=ru_test_0904&amp;utm_content=hn_t3">daoxe.com/pricing</a>. Каждая модель тарифицируется по своей цене в USD; пополнение баланса — несколькими способами, включая USDT.</p>
<h2>Дальше</h2>
<p>Подробный гид по подключению (включая LobeChat, Cherry Studio, OpenCat и ещё десятки клиентов): <a href="https://seven7763.github.io/daoxe-guide/ru/?utm_source=hashnode&amp;utm_medium=organic&amp;utm_campaign=ru_test_0904&amp;utm_content=hn_t3">seven7763.github.io/daoxe-guide/ru</a>. Вопросы — в Telegram @daoxe_ai.</p>
<p><em>Не верьте на слово: три шага выше — регистрация,</em> <code>GET /v1/models</code><em>, один минимальный запрос — проверяемы самостоятельно.</em></p>
]]></content:encoded></item><item><title><![CDATA[The restatement check doesn't catch a model that misread the source]]></title><description><![CDATA[The restatement check doesn't catch a model that misread the source
Follow-up to a Bluesky thread on verifying LLM output. Disclosure up front: I run an OpenAI-compatible gateway (DaoXE), so "did the ]]></description><link>https://daoxe-notes.hashnode.dev/the-restatement-check-doesn-t-catch-a-model-that-misread-the-source</link><guid isPermaLink="true">https://daoxe-notes.hashnode.dev/the-restatement-check-doesn-t-catch-a-model-that-misread-the-source</guid><dc:creator><![CDATA[Seven]]></dc:creator><pubDate>Fri, 04 Sep 2026 14:55:54 GMT</pubDate><content:encoded><![CDATA[<h1>The restatement check doesn't catch a model that misread the source</h1>
<p><em>Follow-up to a Bluesky thread on verifying LLM output. Disclosure up front: I run an OpenAI-compatible gateway (DaoXE), so "did the model actually do what it claims" is a question I have to answer for customers weekly. The method below is provider-agnostic — run it against any endpoint, including ours.</em></p>
<h2>The failure a smart reader pointed out</h2>
<p>A common guard against silent model degradation is a <strong>restatement probe</strong>: after a model produces an answer, ask it to restate the key numbers/claims, and diff the restatement against the original output. If they drift, the leg failed.</p>
<p>The objection, which is correct: <strong>if the model misread the source in the first place, the output and the restatement will agree — on the wrong value.</strong> Restatement checks internal consistency. It cannot detect a <em>consistent confabulation</em>, because both passes reconstruct from the same prose the model already misunderstood.</p>
<p>So restatement catches drift (the model contradicts itself) but not misreading (the model is confidently, consistently wrong). Those are different bugs and they need different guards.</p>
<h2>The stronger check: verify against the source, not the echo</h2>
<p>Instead of asking the model to restate its own conclusion, require it to <strong>cite the exact span in the source</strong> that supports each number/claim, then validate the citation mechanically:</p>
<ol>
<li><p>Ask for structured output: each extracted fact carries a <code>source_quote</code> field copied verbatim from the input.</p>
</li>
<li><p>Programmatically check that <code>source_quote</code> is a substring of the input (normalized for whitespace).</p>
</li>
<li><p>Optionally check the extracted <em>value</em> appears inside the cited span.</p>
</li>
</ol>
<p>If the model misread, step 2 or 3 fails — because the check is anchored to the source text, not to the model's own second opinion. A hallucinated number can't produce a real substring of the input that contains it.</p>
<pre><code class="language-python">def verify_citations(facts, source):
    norm = " ".join(source.split())
    bad = []
    for f in facts:
        q = " ".join(f["source_quote"].split())
        if q not in norm:
            bad.append((f["claim"], "quote not in source"))
        elif f.get("value") and f["value"] not in q:
            bad.append((f["claim"], "value not in cited span"))
    return bad  # empty == every claim is anchored to real source text
</code></pre>
<p>This is still not proof the model <em>understood</em> the span — it proves the model isn't inventing the number out of thin air. That's a weaker, honest claim, and it's the kind worth making: <strong>a signal you can reproduce, not a guarantee you have to trust.</strong></p>
<h2>Why this matters for gateway selection specifically</h2>
<p>When you route one workload across several models (or several upstreams behind one key), the failure mode that bites isn't the crash — it's the leg that answers fluently and wrongly. A citation check like the above is cheap to run per-call and turns "is this gateway's cheap model actually the model I asked for" into a measurable question with a reproducible answer, instead of a vibe.</p>
<p>Run it against your current provider. If the resolved model id in the response and the citation pass-rate both look wrong, you have a specific thing to ask them about — not a vague suspicion.</p>
<p><em>If you've built a better version of this (semantic entailment against the span, not just substring), I'd genuinely like to see it — the thread that prompted this is on Bluesky @daoxe.bsky.social.</em></p>
]]></content:encoded></item><item><title><![CDATA[Model IDs are a dependency. Pin them like one.]]></title><description><![CDATA[Outline

The bug that isn't a bug: your guessed model ID worked, so you shipped it.

Three failure modes of a wrong ID, ranked by how much they'll cost you.

The ten-second test for whether an endpoin]]></description><link>https://daoxe-notes.hashnode.dev/model-ids-are-a-dependency-pin-them-like-one</link><guid isPermaLink="true">https://daoxe-notes.hashnode.dev/model-ids-are-a-dependency-pin-them-like-one</guid><category><![CDATA[AI]]></category><category><![CDATA[Devops]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[backend]]></category><dc:creator><![CDATA[Seven]]></dc:creator><pubDate>Fri, 04 Sep 2026 14:22:21 GMT</pubDate><content:encoded><![CDATA[<h2>Outline</h2>
<ul>
<li><p>The bug that isn't a bug: your guessed model ID worked, so you shipped it.</p>
</li>
<li><p>Three failure modes of a wrong ID, ranked by how much they'll cost you.</p>
</li>
<li><p>The ten-second test for whether an endpoint guesses on your behalf.</p>
</li>
<li><p>Dated vs floating IDs: two different risks, one policy.</p>
</li>
<li><p><code>GET /v1/models</code> is the only source of truth, and it's per-key.</p>
</li>
<li><p><code>models.lock.json</code> and a CI check that fails when a required ID vanishes.</p>
</li>
<li><p>The alias layer: roles, not model names, everywhere in your code.</p>
</li>
<li><p>A lint that stops model IDs leaking back into the codebase.</p>
</li>
<li><p>Retirement playbook: six steps from "it's gone" to "merged".</p>
</li>
<li><p>Channel variants on gateways: read the label, never pattern-match.</p>
</li>
<li><p>Behavioural drift under a pinned ID — signals, not proof.</p>
</li>
</ul>
<h2>Draft</h2>
<p>You typed <code>claude-sonnet-4-5</code> from memory, it returned a completion, and you shipped. Six weeks later the outputs are subtly different, or a deploy fails with a 404 on a Tuesday morning, and the git blame points at a string literal in a file nobody has opened since.</p>
<p>Model IDs are a runtime dependency on someone else's release schedule, and almost nobody treats them like one. We pin <code>requests==2.32.3</code> and then interpolate a model name from a config file we last checked in March.</p>
<p><strong>Disclosure: I work on</strong> <a href="https://daoxe.com/?utm_source=devto&amp;utm_medium=organic&amp;utm_campaign=en_launch&amp;utm_term=en"><strong>daoxe</strong></a><strong>, a gateway that fronts hundreds of models from roughly 25 vendors</strong> — which means I get to watch upstream renames arrive from several directions at once. Everything below works against any endpoint that implements <code>GET /v1/models</code>.</p>
<h3>Three ways a wrong ID fails</h3>
<p><strong>Mode A — a clean error.</strong> <code>404</code> or <code>400</code> with <code>"code": "model_not_found"</code>. This is the <em>good</em> outcome. You find out in development, in the first second, and you fix a typo.</p>
<p><strong>Mode B — alias resolution.</strong> You ask for <code>gpt-4o</code>, the provider resolves it to a dated build, and the response's <code>model</code> field says which one. Fine and normal — as long as you read the echo. If you never look, you've silently accepted whatever the alias points at this month.</p>
<p><strong>Mode C — nearest-match routing.</strong> Some endpoints and proxies try to be helpful: they don't recognise your ID, so they route to the closest thing they do recognise. You asked for a flagship and got something else, with a 200 and no indication anywhere in the response that a substitution happened. This is the mode that costs real money and real trust, and it's invisible in exactly the way that matters.</p>
<h3>The ten-second test for Mode C</h3>
<p>Send an ID that cannot possibly exist, formed by mangling a real one:</p>
<pre><code class="language-bash">curl -s https://api.daoxe.com/v1/chat/completions \
  -H "Authorization: Bearer $LLM_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"claude-sonnet-4-5-TYPO-NOT-A-MODEL",
       "max_tokens":8,"messages":[{"role":"user","content":"hi"}]}' \
  | python3 -c 'import json,sys; d=json.load(sys.stdin); \
print("ERROR:", d["error"]["message"][:80]) if "error" in d else print("SERVED BY:", d.get("model"))'
</code></pre>
<p>If you get an error, every model ID you send is authoritative. If you get a completion, <strong>every model ID you send is advisory</strong> — the endpoint reserves the right to pick for you, and no amount of pinning on your side will help until you know the rule it uses. Run this before you build anything else in this article.</p>
<h3>Dated versus floating</h3>
<p>Two ID styles, two different risks:</p>
<table>
<thead>
<tr>
<th></th>
<th>Floating (<code>claude-sonnet-4-5</code>)</th>
<th>Dated (<code>claude-sonnet-4-5-20250929</code>)</th>
</tr>
</thead>
<tbody><tr>
<td>Behaviour</td>
<td>changes under you, no code change</td>
<td>stable while it exists</td>
</tr>
<tr>
<td>Lifespan</td>
<td>long</td>
<td>retired on a schedule</td>
</tr>
<tr>
<td>Fails as</td>
<td>silent quality drift</td>
<td>a loud 404</td>
</tr>
<tr>
<td>Use for</td>
<td>exploration, prototypes</td>
<td>anything with a recorded eval</td>
</tr>
</tbody></table>
<p>The policy that follows: <strong>exploration floats, production is dated, and every eval artifact records the ID that actually served it</strong> — read from the response's <code>model</code> field, not from what you asked for. That last part is what lets you answer "was this eval run on the same build as that one" three months later.</p>
<h3><code>GET /v1/models</code> is the source of truth, and it's per-key</h3>
<p>Not the docs, not the pricing page, not a blog post. The catalogue endpoint returns the IDs <em>your key</em> is entitled to call, which is frequently a different set from what the vendor sells:</p>
<pre><code class="language-bash">curl -s https://api.daoxe.com/v1/models \
  -H "Authorization: Bearer $LLM_API_KEY" \
  | python3 -c 'import json,sys; [print(m["id"]) for m in json.load(sys.stdin)["data"]]' \
  | sort
</code></pre>
<p>One caveat worth knowing up front: most implementations return IDs and little else. Context windows, tool support, vision support and modality are usually <strong>not</strong> in there, so you still need a small capability sidecar of your own. Don't infer capabilities from the name.</p>
<h3>Lock the catalogue</h3>
<pre><code class="language-python">#!/usr/bin/env python3
"""check_models.py — fail CI when a model ID this repo depends on disappears.

    python3 check_models.py --write      # re-pin (review the diff!)
    python3 check_models.py              # verify
"""
import argparse, json, os, sys, urllib.request

BASE = os.environ["LLM_BASE_URL"].rstrip("/")
KEY = os.environ["LLM_API_KEY"]


def catalogue():
    req = urllib.request.Request(BASE + "/models")
    req.add_header("Authorization", "Bearer " + KEY)
    with urllib.request.urlopen(req, timeout=30) as resp:
        return sorted({m["id"] for m in json.load(resp).get("data", []) if m.get("id")})


def required_ids(path="models.json"):
    """The IDs this repo actually uses — derived from the alias map, never hand-listed."""
    with open(path) as fh:
        return sorted(set(json.load(fh)["roles"].values()))


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--lock", default="models.lock.json")
    ap.add_argument("--write", action="store_true")
    args = ap.parse_args()

    live = catalogue()
    if args.write or not os.path.exists(args.lock):
        with open(args.lock, "w") as fh:
            json.dump({"catalogue": live}, fh, indent=2)
            fh.write("\n")
        print(f"pinned {len(live)} ids to {args.lock}")
        return 0

    with open(args.lock) as fh:
        pinned = set(json.load(fh)["catalogue"])
    live_set, needed = set(live), set(required_ids())

    for mid in sorted(live_set - pinned):
        print(f"note   new in catalogue: {mid}")
    for mid in sorted(pinned - live_set - needed):
        print(f"warn   left the catalogue (unused here): {mid}")
    gone = sorted(needed - live_set)
    for mid in gone:
        print(f"ERROR  required by models.json but not in the catalogue: {mid}")
    return 1 if gone else 0


if __name__ == "__main__":
    sys.exit(main())
</code></pre>
<p>Run it nightly and on every pull request:</p>
<pre><code class="language-yaml">name: model-catalogue
on:
  schedule: [{ cron: "17 6 * * *" }]
  pull_request:
  workflow_dispatch:

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.12" }
      - run: python3 check_models.py
        env:
          LLM_BASE_URL: https://api.daoxe.com/v1
          LLM_API_KEY: ${{ secrets.LLM_API_KEY }}
</code></pre>
<p>The two failure signals mean different things. A <strong>nightly</strong> failure means an upstream retired or renamed something you depend on, and you found out before your users did. A <strong>pull-request</strong> failure means someone added a role pointing at an ID that isn't callable — a typo caught at review time instead of at 2am.</p>
<h3>The alias layer</h3>
<p>Nothing in your application code should contain a model name. Code refers to <em>roles</em>; one file maps roles to exact IDs.</p>
<pre><code class="language-json">{
  "pinned_at": "2026-08-12",
  "roles": {
    "fast":   "claude-sonnet-4-5",
    "deep":   "claude-sonnet-4-5",
    "bulk":   "claude-sonnet-4-5",
    "vision": "claude-sonnet-4-5",
    "embed":  "text-embedding-3-large"
  },
  "notes": {
    "deep": "dated build; re-pin only with an eval run linked in the PR"
  }
}
</code></pre>
<pre><code class="language-python">import functools, json, pathlib

@functools.lru_cache(maxsize=1)
def _roles():
    return json.loads(pathlib.Path("models.json").read_text())["roles"]

def model_for(role):
    try:
        return _roles()[role]
    except KeyError:
        raise KeyError(f"no model pinned for role {role!r} — add it to models.json") from None
</code></pre>
<p>Three things fall out of this for free. A rename becomes a <strong>one-line diff in a reviewable file</strong> instead of a search-and-replace across forty call sites. <code>check_models.py</code> can derive its required set from the same file, so the lock and the code can't disagree. And <code>git log models.json</code> becomes a readable history of what your product was running and when — which is the artifact you'll want the first time someone asks why output quality changed in July.</p>
<p>Then stop the IDs from leaking back in. One test, one line:</p>
<pre><code class="language-bash"># tests/no_hardcoded_model_ids.sh — fails if a model-looking literal appears in src/
! rg -n --glob '!models.json' \
    -e '"(gpt|claude|gemini|deepseek|qwen|kimi|grok|llama)-[a-z0-9._-]+"' src/
</code></pre>
<h3>When an upstream retires a model mid-project</h3>
<ol>
<li><p><strong>Find out from CI, not from users.</strong> That's what the nightly is for.</p>
</li>
<li><p><strong>Read the label before you pick a successor.</strong> Vendors publish migration notes; gateways publish catalogue metadata. Don't infer the replacement from the name — <code>-mini</code>, <code>-fast</code>, <code>-pro</code> and numeric bumps are marketing, not a capability contract.</p>
</li>
<li><p><strong>Freeze a golden set.</strong> Twenty to fifty real requests from your own traffic with their recorded outputs. If you don't have one yet, this is the moment it becomes worth an afternoon; you will need it again.</p>
</li>
<li><p><strong>Run the candidate against the golden set</strong> and diff the things that actually break in production: output format compliance, tool-call rate, refusal rate, response length, and latency. Not vibes.</p>
</li>
<li><p><strong>One PR, one line.</strong> Change <code>models.json</code>, link the eval run in the description, let CI re-verify the lock.</p>
</li>
<li><p><strong>Keep the corpse.</strong> Leave the old ID in the git history of <code>models.lock.json</code> and in your eval artifacts. When you're asked to reproduce a result from two quarters ago, that record is the only thing that will let you say what actually served it.</p>
</li>
</ol>
<h3>Variants and channels on a gateway</h3>
<p>If you route through a gateway, two IDs that look like the same model may reach different upstream channels with different reliability characteristics. Some catalogues label this explicitly — the one I work on tags channels as full-power, official relay, reverse-engineered ("quality not guaranteed"), or promotional, and that labelling is the whole point: you can choose a cheaper or less stable path deliberately, or refuse to. What you should never do is <strong>pattern-match a suffix and assume you know what it means.</strong> Read the catalogue metadata, pick the exact string, pin it.</p>
<h3>Drift under a pinned ID</h3>
<p>Pinning protects you from renames. It does not protect you from an upstream changing what sits behind a name it didn't change. The only handle you have from the outside is behavioural: record how a pinned ID answers a fixed battery of prompts at temperature 0, re-run it on a schedule, and diff.</p>
<pre><code class="language-bash">pipx install git+https://github.com/seven7763/llm-honesty-probe
</code></pre>
<p>Be precise about what that gives you: <strong>signals, not proof.</strong> No probe run from outside can prove which weights served a request, and a change in output can have several innocent explanations. What it does produce is a timestamped record that something changed on a date, which is a much stronger position to argue from than "it feels worse lately."</p>
<p>Pin the ID, alias the role, lock the catalogue, watch the behaviour. It's an afternoon of work, and it converts your loudest class of production surprise into a CI failure.</p>
]]></content:encoded></item><item><title><![CDATA[How to Verify Your API Intermediary Isn't Swapping Models]]></title><description><![CDATA[How to Verify Your API Intermediary Isn't Swapping Models
Disclosure: I run DaoXE, an OpenAI-compatible LLM gateway. Everything below applies to us exactly as it applies to any other provider — that's]]></description><link>https://daoxe-notes.hashnode.dev/how-to-verify-your-api-intermediary-isn-t-swapping-models</link><guid isPermaLink="true">https://daoxe-notes.hashnode.dev/how-to-verify-your-api-intermediary-isn-t-swapping-models</guid><dc:creator><![CDATA[Seven]]></dc:creator><pubDate>Fri, 04 Sep 2026 09:43:14 GMT</pubDate><content:encoded><![CDATA[<h1>How to Verify Your API Intermediary Isn't Swapping Models</h1>
<p><em>Disclosure: I run</em> <a href="https://daoxe.com/?utm_source=hashnode&amp;utm_medium=organic&amp;utm_campaign=ru_test_0904&amp;utm_content=hn_en_t2"><em>DaoXE</em></a><em>, an OpenAI-compatible LLM gateway. Everything below applies to us exactly as it applies to any other provider — that's the point.</em></p>
<h2>Why "is this the model I paid for?" became the main question</h2>
<p>The market of intermediaries between users and LLMs grows faster than any guarantee attached to it. In reviews of API gateways you'll find very different opinions — and a lot of arguments about how to check transparency. That's not an argument against intermediaries as a class; it's an argument for <strong>verifiability</strong>.</p>
<p>Below is a set of checks you can run against any API provider — including us — before you commit budget. We deliberately refuse the "trust us" answer: there's nothing to trust here, and there's something to check.</p>
<h2>Step 0. What a client can actually verify</h2>
<p>Honest about the limits of the method:</p>
<ul>
<li><p><strong>Verifiable from the client side</strong>: which models your account can reach, response format, latency, actual behavior on tasks where a strong and a weak model answer visibly differently, and whether billing matches the traffic you sent.</p>
</li>
<li><p><strong>Not verifiable from the client side</strong>: what infrastructure sits behind the endpoint, or what terms the intermediary has with upstream providers. Anyone claiming otherwise is selling you trust, not a mechanism.</p>
</li>
</ul>
<p>So the goal isn't "catch them or forgive them" — it's making the service's behavior <strong>predictable and measurable for you</strong>.</p>
<h2>Step 1. The model list of your own account</h2>
<p>A standard OpenAI-compatible endpoint exposes the models available to <em>your</em> account:</p>
<pre><code class="language-bash">export API_KEY="your_key"
curl -s https://api.daoxe.com/v1/models \
  -H "Authorization: Bearer ${API_KEY}" | jq -r '.data[].id' | sort
</code></pre>
<p>What to look for:</p>
<ul>
<li><p>the model you're paying for is actually in the list (not just a cheaper cousin);</p>
</li>
<li><p>exact IDs (<code>claude-sonnet-4-5</code>, not <code>gpt-4</code>-style vagueness) — a mismatched ID is the first red flag;</p>
</li>
<li><p>the list changes without notice — that's a question worth asking.</p>
</li>
</ul>
<h2>Step 2. Behavioral tests: tasks where models disagree</h2>
<p>The crudest and most honest method. Pick 5–10 tasks where models you know answer <strong>differently and predictably</strong>:</p>
<ul>
<li><p>rare arithmetic with an exact answer (strong models compute, weak ones approximate);</p>
</li>
<li><p>a prompt with a deliberate trap in the wording (a strong model notices it, a weak one ignores it);</p>
</li>
<li><p>code with a known edge case (a strong model names it);</p>
</li>
<li><p>"what can't you do?" — every model has its own refusal pattern.</p>
</li>
</ul>
<p>Run the same set through the endpoint and compare against the same model's answers in its official interface (wherever you have access). Matching patterns is an indirect sign the claimed model is answering. Divergence is a question to ask.</p>
<p>This is a <strong>signal, not proof</strong>. We're deliberate about that wording: proving token provenance from the client side is impossible, and anyone promising such proof is also selling you trust.</p>
<h2>Step 3. Billing versus traffic</h2>
<p>Keep a simple ledger: tokens sent/received (the <code>usage</code> field in every response) versus what was deducted from your balance. A discrepancy larger than the tariff difference explains isn't a trust question anymore — it's an accounting question for the provider.</p>
<pre><code class="language-python">import requests, json
r = requests.post("https://api.daoxe.com/v1/chat/completions",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={"model": "claude-sonnet-4-5", "messages": [{"role":"user","content":"ping"}]})
print(json.dumps(r.json().get("usage"), indent=2))
</code></pre>
<h2>Step 4. Protocol details that give away a shim</h2>
<ul>
<li><p><strong>Native Anthropic</strong> <code>/v1/messages</code>: if a provider only speaks OpenAI format, Claude Code connects through a translation layer, and parts of the feature set (tool use, cache_control, thinking) behave differently. Check: a direct request to <code>/v1/messages</code> with an <code>x-api-key</code> header — does it work natively or not?</p>
</li>
<li><p><strong>Streaming truncation</strong>: long streamed responses are where cheap channels commonly cut generation short.</p>
</li>
<li><p><strong>Latency as an indirect signal</strong>: a flat 3–5 seconds to first token on a "top" model can mean something else is behind the endpoint. Signal, not verdict — latency depends on geography, load, and context length.</p>
</li>
</ul>
<h2>Step 5. What to ask a provider before paying</h2>
<ol>
<li><p>Show me how I can verify I'm getting the model I asked for. (The answer should be a method, not "we're honest.")</p>
</li>
<li><p>When a channel degrades, do you tell me or silently switch?</p>
</li>
<li><p>How is billing computed — per token or by an "approximate rate"? Where's the deduction history?</p>
</li>
<li><p>Is there a public changelog of models and groups?</p>
</li>
<li><p>What happens to my balance if the service shuts down? (An honest answer to this one is rare and therefore valuable.)</p>
</li>
</ol>
<p>If the answers to 1–2 are elaborate, that <em>is</em> the answer.</p>
<h2>What we do about it</h2>
<p>We run a gateway at <code>https://api.daoxe.com/v1</code> (OpenAI-compatible) with native Anthropic <code>/v1/messages</code>, one balance, per-model USD pricing listed at daoxe.com/pricing. We don't promise "100% stability" and we don't claim we can prove token provenance. We claim less and let you check more:</p>
<ul>
<li><p>your account's model list is open (<code>GET /v1/models</code>);</p>
</li>
<li><p>deduction history and <code>usage</code> in every response — for billing reconciliation;</p>
</li>
<li><p>Claude Code works through the native protocol, not an OpenAI shim;</p>
</li>
<li><p>an open-source CLI checks latency and connectivity <strong>without printing or storing your key</strong> (<a href="https://github.com/seven7763/DaoXE-AI">github.com/seven7763/DaoXE-AI</a>).</p>
</li>
</ul>
<p>Every check in this article applies to us the same as to anyone else. That's the offer: <strong>don't take our word for it — run the three steps.</strong></p>
<p><em>If you found a factual error, say so — we'll fix it and note the correction at the end.</em></p>
]]></content:encoded></item><item><title><![CDATA[20 готовых промпт-шаблонов на русском: перевод, документы, поддержка, код, маркетинг]]></title><description><![CDATA[20 готовых промпт-шаблонов на русском
Рабочие шаблоны для повседневных задач: перевод, документы, поддержка, код, маркетинг. Копируйте, подставляйте текст в {фигурные скобки} — и отправляйте любой мод]]></description><link>https://daoxe-notes.hashnode.dev/20</link><guid isPermaLink="true">https://daoxe-notes.hashnode.dev/20</guid><category><![CDATA[prompts]]></category><category><![CDATA[translation]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[Russian]]></category><dc:creator><![CDATA[Seven]]></dc:creator><pubDate>Fri, 04 Sep 2026 00:53:29 GMT</pubDate><content:encoded><![CDATA[<h1>20 готовых промпт-шаблонов на русском</h1>
<p>Рабочие шаблоны для повседневных задач: перевод, документы, поддержка, код, маркетинг. Копируйте, подставляйте текст в <code>{фигурные скобки}</code> — и отправляйте любой модели, с которой работаете.</p>
<p>Как пользоваться: подставьте свой текст вместо <code>{...}</code>, при необходимости сократите или расширьте роль в начале. Шаблон — это каркас, а не догма: чем конкретнее детали (аудитория, тон, ограничения), тем полезнее результат.</p>
<hr />
<h2>1. Перевод EN ↔ RU (4 шаблона)</h2>
<h3>1.1 Перевод с сохранением стиля</h3>
<blockquote>
<p>Переведи следующий текст на {язык}. Сохрани тон, регистр и структуру оригинала. Не интерпретируй и не добавляй от себя. Если идиома непереводима, дай вариант closest по смыслу и поясни выбор в скобке. Текст: {…}</p>
</blockquote>
<h3>1.2 Техническая документация</h3>
<blockquote>
<p>Переведи фрагмент технической документации на русский. Держи термины единообразно: {список терминов → перевод}. Код, имена переменных и пути не переводи и не меняй. Если в оригинале двусмысленность, отметь её комментарием <code>// NB:</code>. Фрагмент: {…}</p>
</blockquote>
<h3>1.3 Деловое письмо (RU → EN)</h3>
<blockquote>
<p>Переведи это письмо на английский так, чтобы оно звучало как нативная деловая переписка: вежливо, без излишней формальности, с активным залогом. Ключевые факты и цифры не менять. Письмо: {…}</p>
</blockquote>
<h3>1.4 Субтитры/озвучка (EN → RU)</h3>
<blockquote>
<p>Сделай перевод субтитров на русский: уложись в {N} символов на строку, сохрани смысл реплик, адаптируй шутки и культурные отсылки, не оставляй «кальку» с английского. Формат — тот же, что во входе: {…}</p>
</blockquote>
<hr />
<h2>2. Документы и саммари (4 шаблона)</h2>
<h3>2.1 Выжимка длинного документа</h3>
<blockquote>
<p>Сожми документ до {объём}. Структура: 1) главная мысль в одном предложении; 2) 3–7 ключевых тезисов; 3) цифры/факты, которые важны для решения; 4) что в документе не раскрыто. Не выдумывай то, чего в тексте нет. Документ: {…}</p>
</blockquote>
<h3>2.2 Протокол встречи</h3>
<blockquote>
<p>Из заметок ниже сделай протокол встречи: решения (в виде списка), задачи с ответственными и сроками (если упомянуты), открытые вопросы. Стиль — сухой деловой. Заметки: {…}</p>
</blockquote>
<h3>2.3 Договор / ТЗ — объяснение простым языком</h3>
<blockquote>
<p>Объясни разделы этого документа простыми словами: что каждое условие означает на практике, где риски для стороны {мы — заказчик/исполнитель}, какие пункты стоит обсудить до подписания. Юридические формулировки цитируй точно. Документ: {…}</p>
</blockquote>
<h3>2.4 Ответ на претензию/отзыв</h3>
<blockquote>
<p>Напиши ответ на отзыв: {текст отзыва}. Тон: спокойный, по делу, без оправданий и канцелярита. Структура: признание проблемы (если она реальна) → что сделали/сделаем → конкретный следующий шаг. Не обещай того, что не подтверждено.</p>
</blockquote>
<hr />
<h2>3. Поддержка и коммуникации (4 шаблона)</h2>
<h3>3.1 Ответ в техподдержке</h3>
<blockquote>
<p>Пользователь пишет: {обращение}. Продукт: {краткое описание}. Ответь на русском: 1) по делу — решение или следующий шаг; 2) без извинений-заготовок; 3) если информации не хватает — один уточняющий вопрос. Тон — как у коллеги, а не бота.</p>
</blockquote>
<h3>3.2 База знаний по частым вопросам</h3>
<blockquote>
<p>Из переписок ниже извлеки частые проблемы и собери FAQ: вопрос → короткий ответ (2–4 предложения) → когда эскалировать человеку. Переписки: {…}</p>
</blockquote>
<h3>3.3 Черновик письма клиенту</h3>
<blockquote>
<p>Напиши письмо клиенту: {ситуация}. Цель письма: {что должен сделать клиент после прочтения}. Не больше 150 слов, тема письма — отдельной строкой, призыв к действию один.</p>
</blockquote>
<h3>3.4 Коммуникация инцидента</h3>
<blockquote>
<p>Сформулируй статус инцидента для клиентов: что произошло (факты), кого затронуло, текущее состояние, что делаем дальше, когда следующий апдейт. Без оценочных слов «критический», «невероятный». Данные: {…}</p>
</blockquote>
<hr />
<h2>4. Код и ревью (4 шаблона)</h2>
<h3>4.1 Ревью кода</h3>
<blockquote>
<p>Сделай код-ревью: {код}. Порядок: 1) баги и риски (с номерами строк); 2) безопасность; 3) читаемость/структура; 4) производительность — только там, где это важно для данного пути выполнения. По каждому пункту: проблема → почему → как исправить. Не переписывай стиль без необходимости.</p>
</blockquote>
<h3>4.2 Объяснение чужого кода</h3>
<blockquote>
<p>Объясни этот код: что делает каждый блок, какие внешние зависимости и скрытые предусловия, где могут быть побочные эффекты. Код: {…}</p>
</blockquote>
<h3>4.3 Тесты к функции</h3>
<blockquote>
<p>Напиши тесты для функции: {код}. Стек: {pytest / jest / …}. Покрой граничные случаи (пустые значения, границы диапазонов, ошибки). Не тестируй поведение сторонних библиотек.</p>
</blockquote>
<h3>4.4 Миграция/рефакторинг</h3>
<blockquote>
<p>Предложи план миграции {со} на {на}: шаги по порядку, что сломается, как проверить каждый шаг, вариант отката. Код: {…}</p>
</blockquote>
<hr />
<h2>5. Маркетинг и контент (4 шаблона)</h2>
<h3>5.1 Описание продукта</h3>
<blockquote>
<p>Напиши описание {продукта} для {аудитория}. Формат: 1 заголовок (до 60 знаков) + 3 буллита о выгодах + 1 предложение с фактом, который можно проверить. Не используй «лучший», «уникальный», «инновационный» и прочие слова-пустышки.</p>
</blockquote>
<h3>5.2 Пост в соцсети по теме</h3>
<blockquote>
<p>Сделай пост на тему {тема} для {площадка}. Требование: начинай с конкретного (цифра/ситуация), без вступлений про «в современном мире», финальный призыв один. Тон: {…}. Ограничение по длине: {N} знаков.</p>
</blockquote>
<h3>5.3 Анализ конкурентного предложения</h3>
<blockquote>
<p>Вот тексты двух сайтов: {наш} и {конкурент}. Сравни: кому что важно, какие аргументы у нас сильнее, где конкурент честнее/убедительнее, какие формулировки нам не подходят и почему. Без «мы молодцы», только разбор.</p>
</blockquote>
<h3>5.4 Письмо-рассылка (без спама)</h3>
<blockquote>
<p>Напиши письмо для {аудитория} по поводу {повод}. Не больше 120 слов. Заголовок — по делу, без капса и «!!!». Один призыв к действию. Дай также альтернативный заголовок для A/B-теста.</p>
</blockquote>
<hr />
<h2>Что дальше</h2>
<p>Эти шаблоны работают с любой языковой моделью. Если хотите один ключ к нескольким моделям (Claude, GPT, Gemini, DeepSeek и др.) через единый OpenAI-совместимый endpoint <code>https://daoxe.com/v1</code>:</p>
<ul>
<li><p>русскоязычный гид по подключению: <a href="https://seven7763.github.io/daoxe-guide/ru/?utm%5C_source=hashnode&amp;utm%5C_medium=organic&amp;utm%5C_campaign=ru%5C_test%5C_0904&amp;utm%5C_content=hn%5C_ru">https://seven7763.github.io/daoxe-guide/ru/?utm\_source=hashnode&amp;utm\_medium=organic&amp;utm\_campaign=ru\_test\_0904&amp;utm\_content=hn\_ru</a></p>
</li>
<li><p>актуальные цены моделей: <a href="https://daoxe.com/pricing?utm%5C_source=hashnode&amp;utm%5C_medium=organic&amp;utm%5C_campaign=ru%5C_test%5C_0904&amp;utm%5C_content=hn%5C_ru">https://daoxe.com/pricing?utm\_source=hashnode&amp;utm\_medium=organic&amp;utm\_campaign=ru\_test\_0904&amp;utm\_content=hn\_ru</a></p>
</li>
<li><p>оплата и актуальные цены по каждой модели — на daoxe.com/pricing</p>
</li>
<li><p>поддержка: Telegram @daoxe_ai</p>
</li>
</ul>
<h2>Короткий опрос (2 минуты, ответьте в комментариях или в @daoxe_ai)</h2>
<p>Опрос анонимный: не указывайте в ответах имя, email, телефон и платёжные реквизиты.</p>
<ol>
<li><p>Какими AI-моделями/сервисами вы сейчас пользуетесь чаще всего?</p>
</li>
<li><p>Что для вас важнее всего при выборе: цена, стабильность API, качество ответов на русском, оплата (USDT/карта), что-то ещё?</p>
</li>
<li><p>Вам нужнее: единый API-ключ к нескольким моделям — или готовый чат «для общения, не для кода»?</p>
</li>
<li><p>Какой формат оплаты вам удобнее для API-сервисов: крипто-кошельки (USDT/USDC) или карты?</p>
</li>
<li><p>Что было бы полезнее дальше: ещё шаблоны промптов, туториалы по подключению, сравнения моделей — или кейсы «как мы решили задачу X»?</p>
</li>
</ol>
<p>Спасибо! Ответы читаем и используем, чтобы делать сервис полезнее.</p>
]]></content:encoded></item><item><title><![CDATA[One API key, three terminals: driving many models from the shell with llm, mods and aichat]]></title><description><![CDATA[Posted first on DEV.to: https://dev.to/seven7763/one-api-key-three-terminals-driving-many-models-from-the-shell-with-llm-mods-and-aichat-c0b

The fastest way to use an LLM is often not a chat window —]]></description><link>https://daoxe-notes.hashnode.dev/one-api-key-three-terminals-driving-many-models-from-the-shell-with-llm-mods-and-aichat</link><guid isPermaLink="true">https://daoxe-notes.hashnode.dev/one-api-key-three-terminals-driving-many-models-from-the-shell-with-llm-mods-and-aichat</guid><category><![CDATA[cli]]></category><category><![CDATA[AI]]></category><category><![CDATA[llm]]></category><dc:creator><![CDATA[Seven]]></dc:creator><pubDate>Mon, 31 Aug 2026 17:40:57 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>Posted first on DEV.to: <a href="https://dev.to/seven7763/one-api-key-three-terminals-driving-many-models-from-the-shell-with-llm-mods-and-aichat-c0b">https://dev.to/seven7763/one-api-key-three-terminals-driving-many-models-from-the-shell-with-llm-mods-and-aichat-c0b</a></p>
</blockquote>
<p>The fastest way to use an LLM is often not a chat window — it's a pipe. <code>git diff | ... "write a commit message"</code>, <code>... "explain this error" &lt; stderr.log</code>, <code>cat main.py | ... "find the bug"</code>. Three of the best command-line tools for this — <strong><code>llm</code></strong>, <strong><code>mods</code></strong>, and <strong><code>aichat</code></strong> — all accept a custom <strong>OpenAI-compatible endpoint</strong>, which means one key can back all three and give you Claude, GPT, Gemini and more from the shell.</p>
<p>This is the terminal setup, end to end. It's genuinely tool-agnostic: every config below works against any OpenAI-compatible endpoint. I use daoxe as the example endpoint (disclosure below); swap the base URL for your own.</p>
<blockquote>
<p><strong>Disclosure:</strong> I work on <a href="https://daoxe.com/?utm_source=hashnode&amp;utm_medium=organic&amp;utm_campaign=en_launch">daoxe</a>, an OpenAI-compatible gateway (it also speaks Anthropic Messages natively). Everything here is standard OpenAI-compatible config — point these tools at whatever endpoint you trust.</p>
</blockquote>
<p>One convention across all three: <strong>the key lives in an environment variable</strong>, <code>DAOXE_API_KEY</code>, never on the command line (so it doesn't land in your shell history). And model ids are account-scoped — list yours with <code>curl https://daoxe.com/v1/models -H "Authorization: Bearer $DAOXE_API_KEY"</code>.</p>
<hr />
<h2>1. <code>llm</code> (Simon Willison) — the extensible one</h2>
<p><code>llm</code> is the most extensible of the three: a pluggy-based plugin system, a local SQLite log of everything, templates, embeddings. There are two ways to add an OpenAI-compatible gateway.</p>
<h3>Option A — zero-YAML via a plugin</h3>
<p>There's a small plugin, <strong><code>llm-daoxe</code></strong>, that registers the gateway's models and adds a command to pull your account's real model list:</p>
<pre><code class="language-bash">llm install llm-daoxe           # (coming soon on PyPI; until then: clone + `pip install -e .`)
llm keys set daoxe              # store the key (or export DAOXE_API_KEY=...)
llm daoxe models --refresh     # pull your account's real models from GET /v1/models, and cache them
llm -m daoxe/claude-sonnet-4-6 "In one line, what's an OpenAI-compatible gateway?"
git diff | llm -m daoxe/gpt-5.5 -s "Write a concise commit message."
</code></pre>
<p>Model ids are namespaced as <code>daoxe/&lt;model&gt;</code> so they don't collide with other plugins' ids. The key is read only from <code>llm</code>'s key store or <code>DAOXE_API_KEY</code>, and it's never printed.</p>
<h3>Option B — no plugin, native config</h3>
<p>Don't want a plugin? <code>llm</code> natively reads custom OpenAI-compatible models from <code>extra-openai-models.yaml</code> in its config dir:</p>
<pre><code class="language-yaml">- model_id: daoxe/claude-sonnet-4-6
  model_name: claude-sonnet-4-6
  api_base: https://daoxe.com/v1
  api_key_name: daoxe          # matches `llm keys set daoxe`
  can_stream: true
- model_id: daoxe/gpt-5.5
  model_name: gpt-5.5
  api_base: https://daoxe.com/v1
  api_key_name: daoxe
</code></pre>
<p>The plugin's advantage is just convenience: zero YAML plus <code>llm daoxe models --refresh</code> to auto-discover your account's models. Both paths use <code>llm</code>'s built-in OpenAI transport, so streaming and tools behave the same as with official models.</p>
<hr />
<h2>2. <code>mods</code> (charmbracelet) — the pipe-native one</h2>
<p><code>mods</code> is built for Unix pipes and looks great doing it. Open its config with <code>mods --settings</code> and add a provider under <code>apis:</code>:</p>
<pre><code class="language-yaml">default-api: daoxe            # optional: make it the default so you can skip -a
apis:
  daoxe:
    base-url: https://daoxe.com/v1
    api-key-env: DAOXE_API_KEY
    models:                  # use real ids from GET /v1/models; max-input-chars is a client-side value
      claude-sonnet-4-6:
        aliases: ["daoxe-sonnet"]
        max-input-chars: 600000
      gpt-5.5:
        aliases: ["daoxe-gpt"]
        max-input-chars: 600000
</code></pre>
<pre><code class="language-bash">export DAOXE_API_KEY=your-daoxe-key
mods -a daoxe -m claude-sonnet-4-6 "explain this diff" &lt; patch.diff
mods --model daoxe-sonnet "summarize" &lt; README.md      # via alias
</code></pre>
<p><em>Gotcha:</em> <code>mods</code> requires <code>api-key-env</code> to point at an environment variable that <strong>already exists</strong>, or it complains about a missing key. <code>max-input-chars</code> is a client-side truncation setting — tune it to your model, it's not a claim about the gateway.</p>
<hr />
<h2>3. <code>aichat</code> (sigoden) — the all-in-one one</h2>
<p><code>aichat</code> is a single binary that's a CLI, a REPL, and a local server. It uses an <code>openai-compatible</code> client type. Edit <code>~/.config/aichat/config.yaml</code>:</p>
<pre><code class="language-yaml">clients:
  - type: openai-compatible
    name: daoxe
    api_base: https://daoxe.com/v1
    # omit api_key and aichat auto-reads DAOXE_API_KEY (client name → {NAME}_API_KEY)
    models:                     # real ids from GET /v1/models; tune capabilities per model
      - name: claude-sonnet-4-6
        max_input_tokens: 200000
        supports_function_calling: true
      - name: gpt-5.5
        max_input_tokens: 272000
        supports_function_calling: true
</code></pre>
<pre><code class="language-bash">export DAOXE_API_KEY=your-daoxe-key
aichat -m daoxe:claude-sonnet-4-6 "explain this code" &lt; main.py
aichat --list-models | grep daoxe
</code></pre>
<p><em>Nice touch:</em> because the client is named <code>daoxe</code>, <code>aichat</code> automatically looks for <code>DAOXE_API_KEY</code> — you can omit the <code>api_key</code> field entirely. Model selection is <code>client:model</code>, e.g. <code>daoxe:claude-sonnet-4-6</code>.</p>
<hr />
<h2>Which one should you use?</h2>
<table>
<thead>
<tr>
<th>Tool</th>
<th>Best for</th>
<th>Superpower</th>
</tr>
</thead>
<tbody><tr>
<td><code>llm</code></td>
<td>scripting, logging, plugins</td>
<td>SQLite log of every call; huge plugin ecosystem</td>
</tr>
<tr>
<td><code>mods</code></td>
<td>quick pipes</td>
<td>gorgeous TUI output, alias/fallback config</td>
</tr>
<tr>
<td><code>aichat</code></td>
<td>interactive + server</td>
<td>REPL, roles, and a built-in local API server</td>
</tr>
</tbody></table>
<p>They're not mutually exclusive — I keep all three configured against the same key. The same mental model applies to other CLIs too: anything that takes an OpenAI-compatible <code>base_url</code> + key + model id works the same way (point the base URL at the <code>/v1</code> root, use a real id from <code>/v1/models</code>, keep the key in an env var). Verify each tool's current config fields before you rely on them — these tools move fast.</p>
<hr />
<h2>The reason one key matters here</h2>
<p>CLI users tend to script things and forget them: a cron job that summarizes logs, a git hook that drafts commit messages, a shell function that explains errors. When each of those needs a different vendor key, it's a mess of secrets and bills. One OpenAI-compatible key across <code>llm</code>, <code>mods</code>, and <code>aichat</code> — and across every script that calls them — means <strong>one secret to rotate, one bill to read, and model choice as a string.</strong></p>
<p>daoxe is the endpoint I point them at: <code>https://daoxe.com/v1</code>, one key across many models, plus native Anthropic Messages if a tool wants Claude's protocol. It's <strong>not available in mainland China</strong>. As always, none of the config above is daoxe-specific — and because it's a standard endpoint, you can benchmark and verify it rather than trust it.</p>
<hr />
<h2>TL;DR</h2>
<ul>
<li><code>llm</code>, <code>mods</code>, and <code>aichat</code> all take a custom OpenAI-compatible endpoint — one key backs all three.</li>
<li><code>llm</code>: a plugin (<code>llm-daoxe</code>, coming soon) <em>or</em> <code>extra-openai-models.yaml</code>.</li>
<li><code>mods</code>: an <code>apis:</code> block; <code>api-key-env</code> must point at an existing env var.</li>
<li><code>aichat</code>: an <code>openai-compatible</code> client; name it <code>daoxe</code> and it auto-reads <code>DAOXE_API_KEY</code>.</li>
<li>Keep the key in an env var, use exact ids from <code>/v1/models</code>, and pipe away.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Bring your own key to GitHub Copilot: Chat, CLI, App and SDK]]></title><description><![CDATA[Posted first on DEV.to: https://dev.to/seven7763/bring-your-own-key-to-github-copilot-chat-cli-app-and-sdk-with-the-honest-caveat-mod

Most people don't know Copilot can do this yet: as of 2026, GitHu]]></description><link>https://daoxe-notes.hashnode.dev/bring-your-own-key-to-github-copilot-chat-cli-app-and-sdk</link><guid isPermaLink="true">https://daoxe-notes.hashnode.dev/bring-your-own-key-to-github-copilot-chat-cli-app-and-sdk</guid><category><![CDATA[GitHub]]></category><category><![CDATA[copilot]]></category><category><![CDATA[AI]]></category><category><![CDATA[vscode]]></category><dc:creator><![CDATA[Seven]]></dc:creator><pubDate>Mon, 31 Aug 2026 17:28:55 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>Posted first on DEV.to: <a href="https://dev.to/seven7763/bring-your-own-key-to-github-copilot-chat-cli-app-and-sdk-with-the-honest-caveat-mod">https://dev.to/seven7763/bring-your-own-key-to-github-copilot-chat-cli-app-and-sdk-with-the-honest-caveat-mod</a></p>
</blockquote>
<p>Most people don't know Copilot can do this yet: as of 2026, GitHub Copilot has GA'd <strong>Bring Your Own Key (BYOK)</strong>. You can point Copilot Chat (in VS Code), the Copilot CLI, the Copilot app, and the Copilot SDK at <em>any</em> OpenAI-compatible endpoint, Anthropic, or Azure — including local runtimes and third-party gateways.</p>
<p>That's a big deal if you already pay for Copilot but want to run its chat and agent features on a model or a gateway of your choice. This guide covers all four surfaces, with the exact steps and one honest caveat that trips people up.</p>
<blockquote>
<p><strong>Disclosure:</strong> I work on <a href="https://daoxe.com/?utm_source=hashnode&amp;utm_medium=organic&amp;utm_campaign=en_launch">daoxe</a>, an OpenAI-compatible gateway that also speaks the Anthropic Messages protocol. It happens to line up neatly with Copilot's three wire protocols, so I use it as the example — but everything here works with any compatible endpoint. Substitute your own base URL.</p>
</blockquote>
<hr />
<h2>The caveat, first (so nobody's misled)</h2>
<p><strong>BYOK affects Copilot Chat and agent sessions. It does not change inline completions.</strong> Those gray "Tab to accept" suggestions still run on Copilot's own infrastructure and are unaffected by your base URL. If your goal is to change what powers <em>chat/agent</em>, BYOK is for you. If you wanted to swap the model behind inline completions, BYOK won't do it. That's the honest scope.</p>
<hr />
<h2>Why a gateway fits Copilot's three protocols</h2>
<p>Copilot's BYOK supports three provider "types", and a good multi-protocol gateway hits all three:</p>
<ul>
<li><code>openai</code> → the gateway's <code>/v1/chat/completions</code> (default).</li>
<li><code>openai</code> with <code>wireApi: "responses"</code> → the gateway's <code>/v1/responses</code>.</li>
<li><code>anthropic</code> → the gateway's <code>/v1/messages</code> (Claude's native protocol).</li>
</ul>
<p>So one key can serve GPT-shaped, Responses-shaped, and Claude-native requests, depending on which model you pick. Now the four ways to wire it.</p>
<hr />
<h2>1. Copilot CLI (easiest — just env vars)</h2>
<pre><code class="language-bash">export COPILOT_PROVIDER_BASE_URL=https://daoxe.com/v1
export COPILOT_PROVIDER_API_KEY=$DAOXE_API_KEY     # your gateway key, from an env var
export COPILOT_MODEL=gpt-5.5                        # any exact id from GET /v1/models
# optional: COPILOT_PROVIDER_TYPE=openai (default) | anthropic | azure
copilot
</code></pre>
<p>For Claude's native protocol, set <code>COPILOT_PROVIDER_TYPE=anthropic</code> and use the gateway's Anthropic entry point. <strong>Confirm the exact path with <code>curl</code> first</strong> — many gateways take the host root (<code>https://daoxe.com</code>) and let the client append <code>/v1/messages</code>; some want <code>/v1</code>. Don't assume; check your account.</p>
<hr />
<h2>2. Copilot Chat (VS Code)</h2>
<ol>
<li>Command Palette → <strong><code>Chat: Manage Language Models</code></strong>.</li>
<li>Choose <strong>OpenAI Compatible / Custom Endpoint</strong>.</li>
<li><strong>Base URL:</strong> <code>https://daoxe.com/v1</code> (it must serve <code>/chat/completions</code>). VS Code probes <code>GET /models</code> to fill the model dropdown — if your gateway supports it, models auto-populate; otherwise type the Model ID by hand.</li>
<li>Paste your <strong>API Key</strong>, pick a <strong>Model ID</strong> (e.g. <code>claude-sonnet-4-6</code>) → Add Model.</li>
</ol>
<p>The model now shows up in the Copilot Chat model picker alongside the built-ins.</p>
<hr />
<h2>3. Copilot App (desktop)</h2>
<p><strong>Settings → Model Providers → Add provider.</strong> Fill in the endpoint <code>https://daoxe.com/v1</code> and your API key (or choose the <code>anthropic</code> type for Claude-native). Your models then appear in the model picker next to Copilot's hosted ones; the key is stored in your OS keychain.</p>
<hr />
<h2>4. Copilot SDK (for integrators)</h2>
<pre><code class="language-ts">provider: {
  type: "openai",                    // or "anthropic"
  baseUrl: "https://daoxe.com/v1",   // full path; for anthropic, use your account's path
  apiKey: process.env.DAOXE_API_KEY,
  wireApi: "completions",            // use "responses" when you need Responses-API behavior
}
</code></pre>
<hr />
<h2>Getting the model ids right</h2>
<p>Copilot only calls what you tell it to, so use the <strong>exact</strong> ids your endpoint exposes:</p>
<pre><code class="language-bash">curl https://daoxe.com/v1/models -H "Authorization: Bearer $DAOXE_API_KEY"
</code></pre>
<p>Guessed names fail silently or fall back. A gateway with an <strong>account-scoped</strong> <code>/v1/models</code> is ideal here, because the list you see is exactly the list you can call.</p>
<hr />
<h2>Troubleshooting</h2>
<ul>
<li><strong>Model dropdown empty in VS Code?</strong> Your endpoint may not expose <code>GET /models</code>, or the key lacks access. Type the id manually and confirm the key with the <code>curl</code> above.</li>
<li><strong>401 / auth errors?</strong> Check whether the provider expects <code>Authorization: Bearer</code> (OpenAI type) vs <code>x-api-key</code> (some Anthropic setups). Match the provider type to the protocol.</li>
<li><strong>Anthropic type 404s?</strong> It's almost always the base URL shape — root vs <code>/v1</code>. Verify against your gateway's docs.</li>
<li><strong>Chat works but inline completions "didn't change"?</strong> Expected — see the caveat. Inline stays on Copilot's models.</li>
</ul>
<hr />
<h2>What to look for in a BYOK target</h2>
<p>Since you're choosing the backend now, pick one that is:</p>
<ul>
<li><strong>Multi-protocol.</strong> OpenAI Chat Completions <em>and</em> Responses <em>and</em> Anthropic Messages, so all of Copilot's provider types work and Claude runs on its native protocol.</li>
<li><strong>One key, many models,</strong> with an account-scoped <code>/v1/models</code>.</li>
<li><strong>Verifiable.</strong> Fine with you benchmarking it and checking it isn't silently downgrading you (there are open-source tools for exactly this).</li>
<li><strong>Reachable where official billing isn't.</strong> If your card keeps getting declined at an official checkout, a gateway that accepts other payment paths is the difference between shipping and not.</li>
</ul>
<p>daoxe checks those boxes — OpenAI-compatible base URL (<code>https://daoxe.com/v1</code>), native Anthropic Messages, one key across many models — and it's <strong>not available in mainland China</strong>. But BYOK is the point: you're not locked in, so point Copilot at whatever endpoint you trust and can verify.</p>
<hr />
<h2>TL;DR</h2>
<ul>
<li>Copilot BYOK works in <strong>Chat, CLI, App, and SDK</strong> — set a base URL + key.</li>
<li>A multi-protocol gateway covers all three Copilot provider types (openai / responses / anthropic).</li>
<li>Use <strong>exact model ids</strong> from <code>/v1/models</code>.</li>
<li><strong>Inline completions don't change</strong> — BYOK is for chat/agent only.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[One key for CrewAI, AutoGen, LlamaIndex and 8 more: the base_url trick for Python agents]]></title><description><![CDATA[Posted first on DEV.to: https://dev.to/seven7763/one-key-for-crewai-autogen-llamaindex-and-8-more-the-baseurl-trick-for-python-agents-5a3g

Here's a fact that quietly makes multi-model agent developme]]></description><link>https://daoxe-notes.hashnode.dev/one-key-for-crewai-autogen-llamaindex-and-8-more-the-base-url-trick-for-python-agents</link><guid isPermaLink="true">https://daoxe-notes.hashnode.dev/one-key-for-crewai-autogen-llamaindex-and-8-more-the-base-url-trick-for-python-agents</guid><category><![CDATA[Python]]></category><category><![CDATA[AI]]></category><category><![CDATA[llm]]></category><category><![CDATA[agents]]></category><dc:creator><![CDATA[Seven]]></dc:creator><pubDate>Mon, 31 Aug 2026 17:03:03 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>Posted first on DEV.to: <a href="https://dev.to/seven7763/one-key-for-crewai-autogen-llamaindex-and-8-more-the-baseurl-trick-for-python-agents-5a3g">https://dev.to/seven7763/one-key-for-crewai-autogen-llamaindex-and-8-more-the-baseurl-trick-for-python-agents-5a3g</a></p>
</blockquote>
<p>Here's a fact that quietly makes multi-model agent development a lot less painful: <strong>in 2026, virtually every Python agent framework natively supports pointing its underlying LLM at a custom OpenAI-compatible <code>base_url</code>.</strong> No new package, no fork, no framework change. You set one URL and one key, and the framework talks to whatever backend you choose.</p>
<p>That means a single <strong>OpenAI-compatible gateway</strong> — one key that fronts Claude, GPT, Gemini, DeepSeek, Kimi and more, with fallback and one bill — is a natural backend for <em>all</em> of these frameworks at once. The orchestration stays with the framework; the models come from one endpoint.</p>
<p>This post is the map: the exact one-liner for each framework, plus the <strong>one honest gotcha</strong> each has, because those gotchas are what actually cost you an afternoon.</p>
<blockquote>
<p><strong>Disclosure:</strong> I work on <a href="https://daoxe.com/?utm_source=hashnode&amp;utm_medium=organic&amp;utm_campaign=en_launch">daoxe</a>, one such gateway. But nothing below is daoxe-specific — every snippet works against <em>any</em> OpenAI-compatible endpoint. Swap the base URL for your own; the frameworks don't care who's behind it.</p>
</blockquote>
<hr />
<h2>Why this works at all</h2>
<p>Almost every framework builds on the OpenAI Chat Completions shape:</p>
<pre><code>POST {base_url}/chat/completions
Authorization: Bearer &lt;key&gt;
</code></pre>
<p>The only two things that route this call to OpenAI are the <strong>base URL</strong> and the <strong>key</strong>. Change the base URL to a compatible gateway and the same orchestration code now runs against a different backend. Frameworks expose this under slightly different names — <code>base_url</code>, <code>api_base</code>, <code>api_base_url</code> — but it's the same idea.</p>
<p>Two universal rules before the snippets:</p>
<ul>
<li><strong>Model ids are account-scoped.</strong> Don't copy an id from a blog post. List yours with <code>curl {base_url}/models -H "Authorization: Bearer $KEY"</code> and use an exact id.</li>
<li><strong>Keys go in env vars,</strong> never hardcoded. Every snippet below reads from the environment.</li>
</ul>
<hr />
<h2>The 11 frameworks, grouped by mechanism</h2>
<h3>A) Frameworks with a direct <code>base_url</code> parameter</h3>
<p><strong>LangChain (+ LangGraph)</strong> — the biggest ecosystem, so start here:</p>
<pre><code class="language-python">from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="MODEL_ID", base_url="https://daoxe.com/v1", api_key="...")  # key via env
print(llm.invoke("ping").content)

# LangGraph: feed that llm straight into a prebuilt agent
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(llm, tools=[...])
</code></pre>
<p><em>Gotcha:</em> <code>ChatOpenAI</code> targets the official OpenAI spec; non-standard fields some proxies add aren't preserved. Fine for standard responses.</p>
<p><strong>AutoGen (Microsoft AgentChat):</strong></p>
<pre><code class="language-python">from autogen_agentchat.agents import AssistantAgent
from autogen_core.models import ModelInfo
from autogen_ext.models.openai import OpenAIChatCompletionClient

client = OpenAIChatCompletionClient(
    model="MODEL_ID", base_url="https://daoxe.com/v1", api_key="...",
    model_info=ModelInfo(vision=False, function_calling=True, json_output=True,
                         family="unknown", structured_output=True),
)
agent = AssistantAgent("assistant", model_client=client)
</code></pre>
<p><em>Gotcha:</em> a non-OpenAI model id needs <strong>both</strong> <code>base_url</code> <strong>and</strong> <code>model_info</code> (capability flags). Omit <code>model_info</code> and it errors. Set the flags to match the model behind your id.</p>
<p><strong>smolagents (Hugging Face):</strong></p>
<pre><code class="language-python">from smolagents import CodeAgent, OpenAIModel
model = OpenAIModel(model_id="MODEL_ID", api_base="https://daoxe.com/v1", api_key="...")
print(CodeAgent(tools=[], model=model).run("ping"))
</code></pre>
<p><em>Gotcha:</em> pre-1.9 the class was <code>OpenAIServerModel</code> (same params). Uses <code>api_base</code>, not <code>base_url</code>.</p>
<p><strong>Haystack (deepset):</strong></p>
<pre><code class="language-python">from haystack.components.generators.chat import OpenAIChatGenerator
from haystack.dataclasses import ChatMessage
from haystack.utils import Secret

gen = OpenAIChatGenerator(model="MODEL_ID", api_base_url="https://daoxe.com/v1",
                          api_key=Secret.from_env_var("DAOXE_API_KEY"))
print(gen.run([ChatMessage.from_user("ping")])["replies"][0].text)
</code></pre>
<p><em>Gotcha:</em> the param is <code>api_base_url</code> (note the extra word), and the key goes through <code>Secret.from_env_var(...)</code>.</p>
<p><strong>Agno (formerly phidata):</strong></p>
<pre><code class="language-python">from agno.agent import Agent
from agno.models.openai.like import OpenAILike
Agent(model=OpenAILike(id="MODEL_ID", base_url="https://daoxe.com/v1", api_key="...")).print_response("ping")
</code></pre>
<p><em>Gotcha:</em> use <code>OpenAILike</code> (from <code>agno.models.openai.like</code>), not the plain <code>OpenAIChat</code>, for a third-party endpoint.</p>
<h3>B) Frameworks with an explicit provider object</h3>
<p><strong>PydanticAI</strong> — type-safe agents:</p>
<pre><code class="language-python">from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIChatModel
from pydantic_ai.providers.openai import OpenAIProvider

model = OpenAIChatModel("MODEL_ID",
    provider=OpenAIProvider(base_url="https://daoxe.com/v1", api_key="..."))
agent = Agent(model)
print(agent.run_sync("ping").output)
</code></pre>
<p><em>Gotcha:</em> build a generic <code>OpenAIProvider</code> and pass it to <code>OpenAIChatModel</code> — this is the same pattern the docs use for any OpenAI-compatible provider.</p>
<p><strong>LlamaIndex:</strong></p>
<pre><code class="language-python">from llama_index.llms.openai_like import OpenAILike  # pip install llama-index-llms-openai-like

llm = OpenAILike(model="MODEL_ID", api_base="https://daoxe.com/v1", api_key="...",
                 is_chat_model=True, is_function_calling_model=True, context_window=128000)
print(llm.complete("ping"))
</code></pre>
<p><em>Gotcha:</em> set <strong><code>is_chat_model=True</code></strong> or it calls the <code>/completions</code> endpoint instead of <code>/chat/completions</code>. Also set a real <code>context_window</code>.</p>
<h3>C) Frameworks that route through LiteLLM (need the <code>openai/</code> prefix)</h3>
<p><strong>CrewAI:</strong></p>
<pre><code class="language-python">from crewai import LLM, Agent, Crew, Task
llm = LLM(model="openai/MODEL_ID", base_url="https://daoxe.com/v1", api_key="...")
agent = Agent(role="Greeter", goal="Greet briefly.", backstory="Concise.", llm=llm)
task = Task(description="ping", expected_output="A short greeting.", agent=agent)
print(Crew(agents=[agent], tasks=[task]).kickoff())
</code></pre>
<p><em>Gotcha:</em> keep the <strong><code>openai/</code> prefix</strong> on the model id. Without it, CrewAI (via LiteLLM) may match a <em>native</em> provider client for a familiar model name and ignore <code>base_url</code> — you'll get a misleading "API key not valid". Use the API root (<code>.../v1</code>), not <code>.../chat/completions</code>.</p>
<p><strong>DSPy:</strong></p>
<pre><code class="language-python">import dspy
lm = dspy.LM("openai/MODEL_ID", api_base="https://daoxe.com/v1", api_key="...")
dspy.configure(lm=lm)
print(dspy.Predict("question -&gt; answer")(question="ping").answer)
</code></pre>
<p><em>Gotcha:</em> same <code>openai/</code> prefix, and keep <code>/v1</code> in <code>api_base</code> with <strong>no trailing slash</strong> — a trailing slash can double the path and 404.</p>
<h3>D) Wrap the OpenAI client directly</h3>
<p><strong>Instructor</strong> — structured output validated into Pydantic models:</p>
<pre><code class="language-python">import instructor
from openai import OpenAI
from pydantic import BaseModel

class Out(BaseModel):
    text: str

client = instructor.from_openai(OpenAI(base_url="https://daoxe.com/v1", api_key="..."))
print(client.chat.completions.create(model="MODEL_ID", response_model=Out,
        messages=[{"role": "user", "content": "ping"}]))
</code></pre>
<p><em>Gotcha:</em> the default (tool-calling) mode works for tool-capable models; if a model lacks tool calling, pass <code>mode=instructor.Mode.JSON</code>.</p>
<hr />
<h2>The gotchas, in one table</h2>
<table>
<thead>
<tr>
<th>Framework</th>
<th>Param name</th>
<th>The one gotcha</th>
</tr>
</thead>
<tbody><tr>
<td>LangChain</td>
<td><code>base_url</code></td>
<td>non-standard proxy fields not preserved</td>
</tr>
<tr>
<td>AutoGen</td>
<td><code>base_url</code></td>
<td>non-OpenAI id needs <code>model_info</code> too</td>
</tr>
<tr>
<td>smolagents</td>
<td><code>api_base</code></td>
<td>class renamed from <code>OpenAIServerModel</code></td>
</tr>
<tr>
<td>Haystack</td>
<td><code>api_base_url</code></td>
<td>key via <code>Secret.from_env_var</code></td>
</tr>
<tr>
<td>Agno</td>
<td><code>base_url</code></td>
<td>use <code>OpenAILike</code>, not <code>OpenAIChat</code></td>
</tr>
<tr>
<td>PydanticAI</td>
<td><code>OpenAIProvider(base_url=)</code></td>
<td>generic provider, not a vendor class</td>
</tr>
<tr>
<td>LlamaIndex</td>
<td><code>api_base</code></td>
<td><code>is_chat_model=True</code> or it hits <code>/completions</code></td>
</tr>
<tr>
<td>CrewAI</td>
<td><code>base_url</code></td>
<td>model must be <code>openai/&lt;id&gt;</code></td>
</tr>
<tr>
<td>DSPy</td>
<td><code>api_base</code></td>
<td><code>openai/</code> prefix + <strong>no trailing slash</strong></td>
</tr>
<tr>
<td>Instructor</td>
<td>wrap <code>OpenAI(base_url=)</code></td>
<td>use <code>Mode.JSON</code> if no tool calling</td>
</tr>
</tbody></table>
<hr />
<h2>Why one endpoint beats juggling keys</h2>
<p>Multi-agent orchestration is where model sprawl hurts most: a planner on a strong model, workers on a cheap one, a critic on a third — that's three vendors, three keys, three bills, three failure modes. Pointing all of them at one OpenAI-compatible endpoint means:</p>
<p><em>Three agent roles, three models, one endpoint and one bill — model choice is just a string.</em></p>
<ul>
<li><strong>One key, one bill</strong> across every framework and every agent role.</li>
<li><strong>Model choice is a string,</strong> not a new integration. Swap <code>MODEL_ID</code> and the same agent runs on Claude, GPT, or Gemini.</li>
<li><strong>You're not locked to a framework.</strong> The same key runs the same task across all 11.</li>
</ul>
<p>And because it's a standard endpoint, you can <strong>verify it</strong> rather than trust it — run a probe against it and against the official API and compare (I wrote a separate piece on catching silent model swaps).</p>
<p>daoxe is the endpoint I use for this: OpenAI-compatible at <code>https://daoxe.com/v1</code>, plus native Anthropic Messages for Claude-native tools, one key across many models. It's <strong>not available in mainland China</strong>. But the whole point of this article is that none of the code above is daoxe-specific — point it at any compatible endpoint you trust.</p>
<hr />
<h2>TL;DR</h2>
<ul>
<li>Every framework here takes a custom OpenAI-compatible endpoint natively — it's a one-liner.</li>
<li>Watch the param name (<code>base_url</code> vs <code>api_base</code> vs <code>api_base_url</code>) and the per-framework gotcha.</li>
<li>CrewAI/DSPy need the <code>openai/</code> prefix; AutoGen needs <code>model_info</code>; LlamaIndex needs <code>is_chat_model=True</code>.</li>
<li>One key, one bill, model choice as a string — and you can verify the backend instead of trusting it.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[OpenAI cut prices 80%. Here's why I'm not celebrating.]]></title><description><![CDATA[> Posted first on DEV.to at https://dev.to/seven7763/openai-cut-prices-80-heres-why-im-not-celebrating-449o
This week, OpenAI slashed Luna (GPT-5.6) pricing by 80% — from $7 to $1.40 per million token]]></description><link>https://daoxe-notes.hashnode.dev/openai-cut-prices-80-here-s-why-i-m-not-celebrating</link><guid isPermaLink="true">https://daoxe-notes.hashnode.dev/openai-cut-prices-80-here-s-why-i-m-not-celebrating</guid><dc:creator><![CDATA[Seven]]></dc:creator><pubDate>Sat, 01 Aug 2026 20:05:41 GMT</pubDate><content:encoded><![CDATA[<p>&gt; Posted first on DEV.to at <a href="https://dev.to/seven7763/openai-cut-prices-80-heres-why-im-not-celebrating-449o">https://dev.to/seven7763/openai-cut-prices-80-heres-why-im-not-celebrating-449o</a></p>
<p>This week, OpenAI slashed Luna (GPT-5.6) pricing by 80% — from $7 to $1.40 per million tokens. Anthropic shipped Opus 5 at half the price of its predecessor. The LLM API market is in a genuine price war, and for once, developers are winning.</p>
<p>I should be celebrating. I'm not. Here's why.</p>
<p>## The hidden math of cheaper models</p>
<p>When official API prices drop, the economics of model-swapping change in a way nobody talks about.</p>
<p>A bad relay has a simple business model: bill you for Model X, serve you Model Y, pocket the difference. When Model X costs \(15/M tokens and Model Y costs \)0.15/M, the margin is $14.85. That's already attractive.</p>
<p>When Model X drops to $1.40? The absolute margin shrinks — and that's what most people see. But the *relative* margin — the ratio of what you're charged vs what's actually served — can actually grow if budget models get cheaper still. An</p>
<p>The point isn't the exact math. The point is: **price competition between legitimate providers doesn't eliminate the incentive to swap models. It often masks it.** When everyone's prices are dropping, a relay that's 30% cheaper than official looks like aggressive discounting, not fraud.</p>
<p>## "What model are you?" doesn't work</p>
<p>If you're still asking an API endpoint to identify itself, stop. That's a system prompt — trivially spoofed. Every relay operator knows to make the model say "I am Claude, created by Anthropic."</p>
<p>What works: **behavioral fingerprinting at temp=0.**</p>
<p>Give the model a fixed set of tasks that different models handle differently — reasoning problems, long-context recall, strict JSON formatting, refusal boundaries. Run them at temperature 0 to remove sampling noise. Diff the results against the official API.</p>
<p>This isn't theoretical. Here's the actual tool:</p>
<p>```bash</p>
<p>git clone <a href="https://github.com/seven7763/llm-honesty-probe">https://github.com/seven7763/llm-honesty-probe</a></p>
<p>cd llm-honesty-probe</p>
<p>python3 obe --self-test --card</p>
<p>```</p>
<p>That runs a battery of deterministic probes and produces a verdict card: PASS, SUSPICIOUS, or DEGRADED — with the specific probes that triggered each finding. No API key leaves your machine. No judgment calls required. The self-test runs without any API key and takes about 30 seconds.</p>
<p>To test your actual endpoint:</p>
<p>```bash</p>
<p>export LLM_ENDPOINT="<a href="https://your-endpoint/v1">https://your-endpoint/v1</a>"</p>
<p>export LLM_API_KEY="sk-your-key"</p>
<p>python3 -m llm_honesty_probe --compare</p>
<p>```</p>
<p>## Why this matters more now, not less</p>
<p>The price war is genuinely good news. Lower costs mean more experiments, more products, more people who can afford to build with LLMs. I want all of that.</p>
<p>But cheaper official APIs also mean more users signing up for the first time — users who don't know what "Claude" is supposed to sound like, what GPT-5.6's reasoning depth should feel like, or what latency to expect. These are exactly the users a model-swapping relay wants: no baseline for comparison.</p>
<p>Thet. It's **verification that costs less than being wrong.**</p>
<p>## Test your endpoint right now</p>
<p>If you're using any third-party API endpoint — mine included — run the probe. The self-test takes 30 seconds. The compare against your endpoint takes about 2 minutes.</p>
<p>If everything passes, you've got evidence — not proof, but signals — that you're getting what you pay for. If something flags, you know exactly which probe to investigate.</p>
<p>Either way, you know more than you did before. That's the whole point.</p>
<p>---</p>
<p>*Disclosure: I work on [daoxe](<a href="https://daoxe.com/?utm%5C_source=hashnode&amp;utm%5C_medium=organic&amp;utm%5C_campaign=en%5C_launch">https://daoxe.com/?utm\_source=hashnode&amp;utm\_medium=organic&amp;utm\_campaign=en\_launch</a>), an OpenAI-compatible LLM gateway. I also maintain `llm-honesty-probe`, the open-source tool mentioned in this post. The probe treats every endpoint identically — including daoxe. Point it at anyone. If it flags something on my own service, I want to know before you do.*</p>
<p>*The price war is real. The trust problem is real. They're related, and fixing one doesn'y fix the other.*</p>
<p>Tags: #ai #llm #opensource #testingt automaticall defense isn't trus-m llm_honesty_prd they have.</p>
]]></content:encoded></item></channel></rss>