190proof

中文文档
1.0.132发布于 2026/10/20ISCTypeScriptESM + CJS近 30 天下载 1920

An opinionated unified interface for interacting with multiple AI providers including OpenAI, Anthropic, Google, Groq, OpenRouter, and AWS Bedrock. This package provides a consistent API for making requests to different LLM provide

原始使用文档
文档目录23 节
  1. Features
  2. Installation
  3. Usage
  4. Basic Example
  5. Using Different Providers
  6. With Function Calling
  7. With Images
  8. With System Messages
  9. Inspecting Model Routing
  10. Provider Prefix Format
  11. Supported Models
  12. OpenAI
  13. Anthropic
  14. Google
  15. Groq
  16. OpenRouter
  17. Environment Variables
  18. API Reference
  19. callWithRetries(identifier, payload, config?, retries?, chu…
  20. setLogger(sink | null)
  21. parseModelString(model)
  22. Configuration Options
  23. License

190proof

An opinionated unified interface for interacting with multiple AI providers including OpenAI, Anthropic, Google, Groq, OpenRouter, and AWS Bedrock. This package provides a consistent API for making requests to different LLM providers while handling retries, streaming, and multimodal inputs.

Features

Fully-local unified interface across multiple AI providers that includes:

  • 🛠️ Consistent function/tool calling across all providers
  • 💬 Consistent message alternation & system instructions
  • 🖼️ Image format & size normalization
  • 🔄 Automatic retries with configurable attempts
  • 📡 Streaming by default
  • ☁️ Cloud service providers supported (Azure, AWS Bedrock)
  • 🔌 Provider prefix strings for any model without waiting for package updates

Installation

npm install 190proof

Usage

Basic Example

Use any model from any provider with the provider:model-id format:

import { callWithRetries, GenericPayload } from "190proof";

const payload: GenericPayload = {
  model: "openai:gpt-4o-mini",
  messages: [
    {
      role: "user",
      content: "Tell me a joke.",
    },
  ],
};

const response = await callWithRetries("my-request-id", payload);
console.log(response.content);

Using Different Providers

import { callWithRetries, GenericPayload } from "190proof";

// OpenAI
const openaiPayload: GenericPayload = {
  model: "openai:gpt-5",
  messages: [{ role: "user", content: "Hello!" }],
};

// Anthropic
const claudePayload: GenericPayload = {
  model: "anthropic:claude-sonnet-4-5",
  messages: [{ role: "user", content: "Hello!" }],
};

// Google
const geminiPayload: GenericPayload = {
  model: "google:gemini-2.0-flash",
  messages: [{ role: "user", content: "Hello!" }],
};

// Groq
const groqPayload: GenericPayload = {
  model: "groq:llama-3.3-70b-versatile",
  messages: [{ role: "user", content: "Hello!" }],
};

// OpenRouter
const openRouterPayload: GenericPayload = {
  model: "openrouter:google/gemma-4-31b-it:free",
  messages: [{ role: "user", content: "Hello!" }],
};

const response = await callWithRetries("request-id", claudePayload);

With Function Calling

const payload: GenericPayload = {
  model: "openai:gpt-4o",
  messages: [
    {
      role: "user",
      content: "What is the capital of France?",
    },
  ],
  functions: [
    {
      name: "get_country_capital",
      description: "Get the capital of a given country",
      parameters: {
        type: "object",
        properties: {
          country_name: {
            type: "string",
            description: "The name of the country",
          },
        },
        required: ["country_name"],
      },
    },
  ],
};

const response = await callWithRetries("function-call-example", payload);
// response.function_call contains { name: string, arguments: Record<string, any> }

With Images

const payload: GenericPayload = {
  model: "anthropic:claude-sonnet-4-5",
  messages: [
    {
      role: "user",
      content: "What's in this image?",
      files: [
        {
          mimeType: "image/jpeg",
          url: "https://example.com/image.jpg",
        },
      ],
    },
  ],
};

const response = await callWithRetries("image-example", payload);

How images reach the model depends on the provider. OpenAI, Anthropic, and Google get native image blocks. Groq is text-only: images degrade to an inline Image (url) text reference. OpenRouter sends OpenAI-style image_url content parts (the remote URL when present, else a data: URI) — but if the model has no vision-capable endpoints, OpenRouter rejects the request with a routing-layer 404, so the retry loop resends the payload with images degraded to the same inline text references Groq gets, and remembers the model (in-process, until restart) so later calls degrade up front. Messages without image attachments serialize identically either way.

With System Messages

const payload: GenericPayload = {
  model: "google:gemini-2.0-flash",
  messages: [
    {
      role: "system",
      content: "You are a helpful assistant that speaks in a friendly tone.",
    },
    {
      role: "user",
      content: "Tell me about yourself.",
    },
  ],
};

const response = await callWithRetries("system-message-example", payload);

Inspecting Model Routing

Use parseModelString to see how a model string will be routed:

import { parseModelString } from "190proof";

parseModelString("openai:gpt-7");
// → { provider: "openai", modelId: "gpt-7" }

parseModelString("openrouter:org/model-name:free");
// → { provider: "openrouter", modelId: "org/model-name:free" }

Provider Prefix Format

The model string format is provider:model-id, where the provider prefix is one of:

PrefixProvider
openaiOpenAI
anthropicAnthropic
googleGoogle (Gemini)
groqGroq
openrouterOpenRouter

The prefix is stripped before sending to the API, so the model ID should be exactly what the provider expects (e.g. "openai:gpt-4o" sends "gpt-4o" to OpenAI).

Supported Models

These models are tested. You can use any model with the provider:model-id format.

OpenAI

  • openai:gpt-5
  • openai:gpt-5-mini
  • openai:gpt-4.1
  • openai:gpt-4.1-mini
  • openai:gpt-4.1-nano
  • openai:gpt-4o
  • openai:gpt-4o-mini
  • openai:o3-mini
  • openai:o1-preview
  • openai:o1-mini

Anthropic

  • anthropic:claude-opus-4-5
  • anthropic:claude-sonnet-4-5
  • anthropic:claude-haiku-4-5
  • anthropic:claude-opus-4-1
  • anthropic:claude-opus-4-20250514
  • anthropic:claude-sonnet-4-20250514
  • anthropic:claude-3-5-sonnet-20241022
  • anthropic:claude-3-5-haiku-20241022

Google

  • google:gemini-3.1-flash-lite-preview
  • google:gemini-3-flash-preview
  • google:gemini-2.5-flash-preview-04-17
  • google:gemini-2.0-flash
  • google:gemini-2.0-flash-exp-image-generation
  • google:gemini-1.5-pro-latest

Groq

  • groq:llama-3.3-70b-versatile
  • groq:llama3-70b-8192
  • groq:qwen/qwen3-32b
  • groq:deepseek-r1-distill-llama-70b

OpenRouter

  • openrouter:google/gemma-4-31b-it:free
  • openrouter:google/gemma-4-31b-it

Environment Variables

Set the following environment variables for the providers you want to use:

# OpenAI
OPENAI_API_KEY=your-openai-api-key

# Anthropic
ANTHROPIC_API_KEY=your-anthropic-api-key

# Google
GEMINI_API_KEY=your-gemini-api-key

# Groq
GROQ_API_KEY=your-groq-api-key

# OpenRouter
OPENROUTER_API_KEY=your-openrouter-api-key

# AWS Bedrock (for Anthropic via Bedrock)
AWS_ACCESS_KEY_ID=your-aws-access-key
AWS_SECRET_ACCESS_KEY=your-aws-secret-key

API Reference

callWithRetries(identifier, payload, config?, retries?, chunkTimeoutMs?)

Main function to make requests to any supported AI provider.

Parameters

  • identifier: string | string[] - Unique identifier for the request (used for logging)
  • payload: GenericPayload - Request payload containing model, messages, and optional functions
  • config: OpenAIConfig | AnthropicAIConfig - Optional configuration for the specific provider
  • retries: number - Number of retry attempts (default: 5)
  • chunkTimeoutMs: number - Timeout for streaming chunks in ms (default: 15000)

Optional per-request knobs live on payload (GenericPayload):

  • payload.requestTimeoutMs: number - Per-attempt HTTP timeout in ms (default: 120000), honored by every adapter — except streaming OpenRouter attempts, which it deliberately does NOT bound (see below). For OpenRouter's non-streaming transport the default is 180000.
  • payload.streaming: boolean - OpenRouter-only (default: true). Streams the completion over SSE. A streaming attempt is bounded by two independent timers instead of requestTimeoutMs: streamTimeoutMs (total wall clock, default 600000) and the per-useful-chunk stall timeout (chunkTimeoutMs argument, default 15000). A chunk is "useful" only if it advances content, reasoning, tool-call fragments, finish_reason, or usage — SSE comment keep-alives (: OPENROUTER PROCESSING) and role-only deltas don't reset the stall timer, so a hung provider dies within one stall window while a healthy long generation can run to the total budget. Set streaming: false for the old single-JSON-body transport.
  • payload.streamTimeoutMs: number - OpenRouter-only: total wall-clock budget per streaming attempt (default: 600000).
  • payload.streamDeadlineAt: number - OpenRouter-only: absolute deadline (epoch ms) for the whole call including retries — the caller's turn budget. Each attempt gets min(streamTimeoutMs, deadline - now), and once under 10s remain the call fails fast instead of starting a generation that cannot be delivered. Use it whenever the caller has its own timeout: a per-attempt budget alone is re-granted on every retry and can outlive that timeout.
  • payload.thinkingConfig: Record<string, unknown> - Google-only: forwarded verbatim as generationConfig.thinkingConfig on the Gemini request — e.g. { thinkingBudget: 0 } to disable thinking, { thinkingLevel: "HIGH" } on models that take a level. Ignored by all other adapters; shapes are model-specific and validated by Google, not the SDK.
  • payload.reasoningEffort: string - OpenAI and OpenRouter reasoning effort. Valid values are model-dependent (none/minimal/low/medium/high/xhigh/max). OpenRouter: sent as the nested reasoning: { effort } object — the canonical form, and the only one that accepts max (the flat reasoning_effort field caps at xhigh). Direct OpenAI: sent flat as reasoning_effort; max is rejected there, and reasoning-by-default models (the gpt-5.6 family) reject function tools on /chat/completions with a 400 unless this is explicitly "none" — their implicit default is medium. Via OpenRouter the same models accept tools at any effort (OpenRouter fronts /v1/responses), so omitting this runs them at their native default. Ignored by all other adapters.

When a streaming attempt is cut at its total deadline and prose has already arrived, the partial answer is returned with truncated: true on the response rather than discarded — those tokens were generated and billed, so throwing them away costs money and gives the user nothing. Surface such a reply as incomplete. Salvage never applies to tool-call turns (half-streamed arguments are unparseable JSON), to stalls (the provider died mid-thought), or to caller aborts. When nothing is salvageable, the discard is logged with an approximate token count — aborted attempts never receive OpenRouter's usage chunk, so that log line is the only record of the wasted spend.

OpenRouter retries also perform moderation eviction: a provider content-moderation rejection (e.g. "Upstream error from Alibaba: Output data may contain inappropriate content.") is deterministic for a given payload, so on the first one the refusing provider is removed from the request's provider preferences (ignore += slug, order -= slug) and every remaining attempt reroutes to the next provider. Non-moderation errors retry with unchanged preferences, and fallbackModel still applies if the whole pool refuses.

  • payload.signal: AbortSignal - Caller-supplied cancellation. When it aborts, the in-flight provider request is cancelled and callWithRetries rejects immediately — it does not retry or fall back (both the retry loop and the fallback branch bail on signal.aborted). Threaded to the underlying fetch/axios/SDK call of each provider.

Returns

Promise<ParsedResponseMessage>:

interface ParsedResponseMessage {
  role: "assistant";
  content: string | null;
  function_call: FunctionCall | null;
  function_calls: FunctionCall[];
  files: File[]; // For models that return files (e.g., image generation)
  // Who actually served the response: OpenRouter's upstream provider from the
  // response body (e.g. "Baidu"), or the SDK provider name ("anthropic", ...)
  // for direct providers. On fallback, reflects the model that answered.
  provider?: string;
  usage: {
    prompt_tokens: number;
    completion_tokens: number;
    total_tokens: number;
    // Reasoning/thinking tokens spent before the visible answer; currently
    // populated from Google's usageMetadata.thoughtsTokenCount.
    thoughts_tokens?: number;
  } | null; // null when streaming
}

setLogger(sink | null)

190proof logs each call and retry to the console. Route those lines to your own logger, or pass null to silence them (useful when 190proof is a dependency of a library):

import { setLogger } from "190proof";

setLogger(null); // silent
setLogger({ log: myLog.info, warn: myLog.warn, error: myLog.error });

Errors thrown by callWithRetries never carry the HTTP request (and so never the API key); provider status and response body stay on error.response.

parseModelString(model)

Parses a model string into its provider and model ID components.

Parameters

  • model: string - A model string in "provider:model-id" format

Returns

{ provider: Provider, modelId: string }

Configuration Options

OpenAI Config

interface OpenAIConfig {
  service: "azure" | "openai";
  apiKey: string;
  /**
   * Optional base URL. Defaults to `https://api.openai.com/v1`. Set to point
   * at any OpenAI-compatible endpoint (e.g. a self-hosted proxy). The path
   * `/chat/completions` is appended automatically. Ignored for Azure.
   */
  baseUrl?: string;
  orgId?: string;
  modelConfigMap?: Record<
    string,
    {
      resource: string;
      deployment: string;
      apiVersion: string;
      apiKey: string;
      endpoint?: string;
    }
  >;
}

To talk to an OpenAI-compatible server instead of OpenAI itself:

await callWithRetries(
  "my-identifier",
  {
    model: "openai:gpt-4o-mini",
    messages: [{ role: "user", content: "hi" }],
  },
  {
    service: "openai",
    apiKey: process.env.SOME_SERVER_API_KEY,
    baseUrl: "https://your-proxy.example.com/v1",
  },
);

Anthropic Config

interface AnthropicAIConfig {
  service: "anthropic" | "bedrock";
}

License

ISC

版本与发布记录

190proof 共发布 127 个版本,首次发布于 2024/04/28;过去 12 个月发布了 61 个版本。

版本发布日期距今
1.0.132latest2026/10/2019 天前
1.0.1312026/10/1722 天前
1.0.1292026/10/1524 天前
1.0.1282026/10/1524 天前
1.0.1272026/10/081 个月前
1.0.1262026/10/081 个月前

关于 190proof 的常见问题

190proof 是什么?

An opinionated unified interface for interacting with multiple AI providers including OpenAI, Anthropic, Google, Groq, OpenRouter, and AWS Bedrock. This package provides a consistent API for making requests to different LLM provide

如何安装 190proof?

在项目目录运行 npm install 190proof,或使用 pnpm add / yarn add / bun add。

190proof 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

190proof 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

190proof 可以商用吗?

190proof 使用 ISC 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

190proof 的下载量有多少?

根据 npm 官方下载统计,190proof 在 2026/10/04 – 2026/11/02 这 30 天内被下载 1,920 次,日均约 64 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

190proof 还在维护吗?

最新版本 1.0.132 发布于 2026/10/20(19 天前);至今共发布 127 个版本,其中过去 12 个月发布了 61 个;首次发布于 2024/04/28。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。