llm.azure_foundry.js 2.64 KB
/**
 * Azure AI Foundry OpenAI-compatible provider.
 *
 * Azure Foundry endpoints used by this repo expose an OpenAI-compatible
 * `/chat/completions` API when `LLM_BASE_URL` points at the resource's
 * `/openai/v1` base URL. Request/stream parsing is therefore identical to the
 * OpenAI provider. Azure resource keys use the `api-key` header, and there is
 * no safe global default host for Azure deployments, so baseUrl must be
 * injected by the key provider.
 *
 * Filename `llm.azure_foundry.js` registers provider key `azure_foundry`.
 */

import { LLMError } from '../errors.js'
import OpenAIProvider from './llm.openai.js'

export default class AzureFoundryProvider extends OpenAIProvider {
  /**
   * Azure 将内容安全拦截作为 HTTP 400 返回。对剧情运行时,这通常是供应商对完整 prompt
   * 或生成方向的拒绝,不等价于玩家请求参数错误;允许路由层切到下一个供应商。
   * @protected
   * @param {Response} res
   * @param {unknown} body
   * @param {string} reason
   */
  httpErrorInfo(res, body, reason) {
    if (isContentFilterError(body)) {
      return {
        reason: 'contentFilter',
        retryable: true,
        message: '[azure_foundry] content filtered',
        provider_request_id: res.headers.get('x-request-id'),
      }
    }
    return super.httpErrorInfo(res, body, reason)
  }

  /**
   * @protected
   * @param {import('../index.js').KeyLease} lease
   * @returns {Record<string, string>}
   */
  chatCompletionsHeaders(lease) {
    return {
      'content-type': 'application/json',
      'api-key': lease.api_key,
    }
  }

  /**
   * @protected
   * @param {string|undefined} model
   * @returns {string}
   */
  maxTokensParamName(model) {
    return isGpt5Model(model) ? 'max_completion_tokens' : 'max_tokens'
  }

  /**
   * Azure Foundry OpenAI v1 rejects the non-standard `thinking` parameter.
   * @protected
   * @returns {boolean}
   */
  supportsThinkingParam() {
    return false
  }

  /** @returns {string} */
  defaultBaseUrl() {
    throw new LLMError({
      provider: this.provider,
      reason: 'badRequest',
      retryable: false,
      message: 'azure_foundry requires LLM_BASE_URL, for example https://<resource>.services.ai.azure.com/openai/v1',
    })
  }
}

/**
 * @param {string|undefined} model
 * @returns {boolean}
 */
function isGpt5Model(model) {
  return /^gpt-5(?:[.-]|$)/i.test(String(model || ''))
}

function isContentFilterError(body) {
  if (!body || Object.getPrototypeOf(body) !== Object.prototype) return false
  const error = body.error
  if (!error || Object.getPrototypeOf(error) !== Object.prototype) return false
  return error.code === 'content_filter'
}