supplier+deploy/mainnet: optional OPENAI_API_KEY for DeepSeek (and other hosted OpenAI-compatible APIs)

Adds an optional OPENAI_API_KEY env var that supplier/src/openai.ts forwards as
`Authorization: Bearer <key>`. Empty/unset preserves the ChatMock behaviour
(no auth header). Unlocks pointing the openai backend at DeepSeek directly
(https://api.deepseek.com) — no local proxy needed.

New compose: deploy/mainnet/docker-compose.supplier-deepseek.yml (third mainnet
supplier, model deepseek-v4-pro, mp-suppliers-deepseek.vector.apexfusion.org).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
david
2026-05-27 11:23:57 +02:00
parent d59d3f56b8
commit 1466442870
6 changed files with 151 additions and 2 deletions

View File

@@ -42,6 +42,20 @@ PORT=8080
# generous. Increase if you see ollama_timeout reasons in logs.
OLLAMA_TIMEOUT_MS=120000
# ─── Optional — OpenAI-compatible backend (LLM_BACKEND=openai) ────────────
# Switch the chat job runner to POST /v1/chat/completions at OPENAI_BASE_URL
# instead of Ollama. Used by the mainnet ChatMock-backed supplier and any
# hosted OpenAI-compatible API (OpenAI, DeepSeek, etc.).
# LLM_BACKEND=openai
# OPENAI_BASE_URL=http://marketplace-mainnet-chatmock:8000 # or https://api.deepseek.com
# OPENAI_TIMEOUT_MS=180000
# OPENAI_API_KEY — optional bearer token. When set, the supplier forwards it
# as `Authorization: Bearer <key>` on every /v1/chat/completions request.
# Leave empty for endpoints that don't require auth (ChatMock, other local
# proxies). REQUIRED for hosted APIs (DeepSeek, OpenAI). SECRET.
# OPENAI_API_KEY=
# ─── Optional (not in supplier/src/config.ts; consumed by index.ts boot) ──
# Pre-derived public-key + pkh + bech32 address. M1-D will derive these from
# SUPPLIER_PRIV_KEY_HEX automatically; until then index.ts reads them from

View File

@@ -65,6 +65,12 @@ export interface SupplierConfig {
ollamaUrl: string;
/** Empty string when capabilityKind="tts" or llmBackend="ollama". */
openaiBaseUrl: string;
/**
* Bearer token forwarded as `Authorization: Bearer <key>` when llmBackend="openai".
* Empty string means no header is sent (correct for ChatMock and other localhost
* proxies). Required for hosted OpenAI-compatible APIs (DeepSeek, OpenAI, etc.).
*/
openaiApiKey: string;
/** Empty string when capabilityKind="chat". */
piperUrl: string;
advertRef: AdvertRef;
@@ -193,6 +199,8 @@ export function loadConfig(env: Record<string, string | undefined>): SupplierCon
openaiTimeoutMs = Number(openaiTimeoutStr);
}
const openaiApiKey = env.OPENAI_API_KEY ?? "";
const piperTimeoutStr = env.PIPER_TIMEOUT_MS;
let piperTimeoutMs = 120_000;
if (piperTimeoutStr !== undefined && piperTimeoutStr !== "") {
@@ -212,6 +220,7 @@ export function loadConfig(env: Record<string, string | undefined>): SupplierCon
ogmiosUrl,
ollamaUrl,
openaiBaseUrl,
openaiApiKey,
piperUrl,
advertRef,
networkId,

View File

@@ -91,6 +91,7 @@ export async function runChatJob(params: RunChatJobParams): Promise<void> {
model: advert.model,
messages: requestBody.messages,
timeoutMs: deps.config.openaiTimeoutMs,
apiKey: deps.config.openaiApiKey,
});
} else {
inference = await ollama.callOllama({

View File

@@ -33,6 +33,12 @@ export interface CallOpenAiParams {
model: string;
messages: ChatMessage[];
timeoutMs: number;
/**
* Optional bearer token. When non-empty, an `authorization: Bearer <apiKey>`
* header is sent. Omit (or pass "") for endpoints that don't require auth,
* e.g. the ChatMock localhost proxy.
*/
apiKey?: string;
}
export interface OpenAiResult {
@@ -62,10 +68,15 @@ function isAbortError(err: unknown): boolean {
}
export async function callOpenAi(params: CallOpenAiParams): Promise<OpenAiResult> {
const { baseUrl, model, messages, timeoutMs } = params;
const { baseUrl, model, messages, timeoutMs, apiKey } = params;
const url = `${baseUrl}/v1/chat/completions`;
const body = JSON.stringify({ model, messages, stream: false });
const headers: Record<string, string> = { "content-type": "application/json" };
if (apiKey) {
headers.authorization = `Bearer ${apiKey}`;
}
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -74,7 +85,7 @@ export async function callOpenAi(params: CallOpenAiParams): Promise<OpenAiResult
try {
response = await fetch(url, {
method: "POST",
headers: { "content-type": "application/json" },
headers,
body,
signal: controller.signal,
});