mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-15 14:22:43 +00:00
fix(providers): align Fireworks integration with project policy
This commit is contained in:
parent
c97d9a4c07
commit
31152ae108
24 changed files with 55 additions and 178 deletions
|
|
@ -2,15 +2,12 @@
|
|||
# Copy this file to .env and fill in your API keys
|
||||
|
||||
# =============================================================================
|
||||
# LLM PROVIDER (Fireworks AI) — preferred BYOK provider
|
||||
# LLM PROVIDER (Fireworks AI)
|
||||
# =============================================================================
|
||||
# Fireworks AI — fastest inference for production AI, 300+ models, one API key.
|
||||
# Get your key at: https://app.fireworks.ai/settings/users/api-keys
|
||||
# Address models directly by catalog ID, e.g.
|
||||
# accounts/fireworks/models/kimi-k2p6, accounts/fireworks/models/glm-5p2
|
||||
# FIREWORKS_API_KEY=
|
||||
# FIREWORKS_BASE_URL=https://api.fireworks.ai/inference/v1 # Override default base URL
|
||||
|
||||
# =============================================================================
|
||||
# LLM PROVIDER (OpenRouter)
|
||||
# =============================================================================
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Fireworks AI">
|
||||
<path d="M15.9851 19.1274C15.0969 19.1274 14.2999 18.6001 13.96 17.7838L9.86258 8H12.2608L15.9991 16.9499L19.7339 8H22.1321L18.0102 17.7873C17.6686 18.6001 16.8733 19.1274 15.9851 19.1274Z" fill="#FFFFFF"/>
|
||||
<path d="M21.3316 23.8029C20.4469 23.8029 19.6533 23.2792 19.31 22.4698C18.9649 21.6535 19.1436 20.7215 19.7672 20.0891L27.2299 12.5302L28.1618 14.7287L21.3298 21.636L31.068 21.5817L32 23.7802L21.3333 23.8065L21.3298 23.8029H21.3316Z" fill="#FFFFFF"/>
|
||||
<path d="M0 23.7766L0.931955 21.5781L10.6702 21.6324L3.83993 14.7234L4.77189 12.5249L12.2345 20.0838C12.8582 20.7145 13.0386 21.65 12.6918 22.4645C12.3484 23.2756 11.5513 23.7977 10.6702 23.7977L0.00350359 23.7731L0 23.7766Z" fill="#FFFFFF"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 848 B |
|
|
@ -1,5 +0,0 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Fireworks AI">
|
||||
<path d="M15.9851 19.1274C15.0969 19.1274 14.2999 18.6001 13.96 17.7838L9.86258 8H12.2608L15.9991 16.9499L19.7339 8H22.1321L18.0102 17.7873C17.6686 18.6001 16.8733 19.1274 15.9851 19.1274Z" fill="#6720FF"/>
|
||||
<path d="M21.3316 23.8029C20.4469 23.8029 19.6533 23.2792 19.31 22.4698C18.9649 21.6535 19.1436 20.7215 19.7672 20.0891L27.2299 12.5302L28.1618 14.7287L21.3298 21.636L31.068 21.5817L32 23.7802L21.3333 23.8065L21.3298 23.8029H21.3316Z" fill="#6720FF"/>
|
||||
<path d="M0 23.7766L0.931955 21.5781L10.6702 21.6324L3.83993 14.7234L4.77189 12.5249L12.2345 20.0838C12.8582 20.7145 13.0386 21.65 12.6918 22.4645C12.3484 23.2756 11.5513 23.7977 10.6702 23.7977L0.00350359 23.7731L0 23.7766Z" fill="#6720FF"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 848 B |
|
|
@ -56,15 +56,12 @@ afterEach(() => {
|
|||
})
|
||||
|
||||
describe('onboarding Picker', () => {
|
||||
it('features Nous Portal and Fireworks, and hides other providers behind a disclosure', () => {
|
||||
it('features Nous Portal and hides other providers behind a disclosure', () => {
|
||||
setProviders([provider('anthropic', 'Anthropic Claude'), provider('nous', 'Nous Portal')])
|
||||
render(<Picker ctx={ctx} />)
|
||||
|
||||
expect(screen.getByText('Nous Portal')).toBeTruthy()
|
||||
// Fireworks is the preferred BYOK provider — a second hero card after Nous.
|
||||
expect(screen.getByText('Fireworks AI')).toBeTruthy()
|
||||
// Both hero cards carry a Recommended badge; the plain rows below do not.
|
||||
expect(screen.getAllByText('Recommended')).toHaveLength(2)
|
||||
expect(screen.getByText('Recommended')).toBeTruthy()
|
||||
expect(screen.queryByText('Anthropic API Key')).toBeNull()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Other providers' }))
|
||||
|
|
@ -73,25 +70,14 @@ describe('onboarding Picker', () => {
|
|||
expect(screen.getByRole('button', { name: 'Collapse' })).toBeTruthy()
|
||||
})
|
||||
|
||||
it('still features Fireworks when Nous Portal is absent', () => {
|
||||
it('shows every provider directly when Nous Portal is absent', () => {
|
||||
setProviders([provider('anthropic', 'Anthropic Claude'), provider('openai-codex', 'OpenAI Codex / ChatGPT')])
|
||||
render(<Picker ctx={ctx} />)
|
||||
|
||||
expect(screen.getByText('Anthropic API Key')).toBeTruthy()
|
||||
expect(screen.getByText('OpenAI OAuth (ChatGPT)')).toBeTruthy()
|
||||
expect(screen.queryByText('Other sign-in options')).toBeNull()
|
||||
// Fireworks stays featured even without Nous — exactly one Recommended badge.
|
||||
expect(screen.getByText('Fireworks AI')).toBeTruthy()
|
||||
expect(screen.getAllByText('Recommended')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('routes the Fireworks hero card into the API-key form', () => {
|
||||
setProviders([provider('nous', 'Nous Portal')])
|
||||
render(<Picker ctx={ctx} />)
|
||||
|
||||
fireEvent.click(screen.getByText('Fireworks AI'))
|
||||
|
||||
expect($desktopOnboarding.get().mode).toBe('apikey')
|
||||
expect(screen.queryByText('Recommended')).toBeNull()
|
||||
})
|
||||
|
||||
it('offers "choose later" on first run and persists the skip', () => {
|
||||
|
|
|
|||
|
|
@ -28,10 +28,9 @@ import {
|
|||
import type { ModelOptionProvider, OAuthProvider } from '@/types/hermes'
|
||||
|
||||
import { DocsLink, FlowPanel, Status } from './flow'
|
||||
import { FeaturedFireworksRow, FeaturedProviderRow, KeyProviderRow, ProviderRow, sortProviders } from './providers'
|
||||
import { FeaturedProviderRow, KeyProviderRow, ProviderRow, sortProviders } from './providers'
|
||||
|
||||
export {
|
||||
FeaturedFireworksRow,
|
||||
FeaturedProviderRow,
|
||||
KeyProviderRow,
|
||||
ProviderRow,
|
||||
|
|
@ -56,18 +55,18 @@ export interface ApiKeyOption {
|
|||
}
|
||||
|
||||
const API_KEY_OPTIONS: ApiKeyOption[] = [
|
||||
{
|
||||
id: 'fireworks',
|
||||
name: 'Fireworks AI',
|
||||
envKey: 'FIREWORKS_API_KEY',
|
||||
docsUrl: 'https://app.fireworks.ai/settings/users/api-keys'
|
||||
},
|
||||
{
|
||||
id: 'openrouter',
|
||||
name: 'OpenRouter',
|
||||
envKey: 'OPENROUTER_API_KEY',
|
||||
docsUrl: 'https://openrouter.ai/keys'
|
||||
},
|
||||
{
|
||||
id: 'fireworks',
|
||||
name: 'Fireworks AI',
|
||||
envKey: 'FIREWORKS_API_KEY',
|
||||
docsUrl: 'https://app.fireworks.ai/settings/users/api-keys'
|
||||
},
|
||||
{
|
||||
id: 'openai',
|
||||
name: 'OpenAI',
|
||||
|
|
@ -408,9 +407,8 @@ export function Picker({ ctx }: { ctx: OnboardingContext }) {
|
|||
const { t } = useI18n()
|
||||
const { localEndpoint, manual, mode, providers } = useStore($desktopOnboarding)
|
||||
const [showAll, setShowAll] = useState(readShowAll)
|
||||
// Which key-form option to preselect when we flip to 'apikey' mode. Set by the
|
||||
// Fireworks hero card (its BYOK key) and the OpenRouter row; the generic
|
||||
// "I have an API key" link leaves it undefined and lands on the first option.
|
||||
// Which key-form option to preselect when we flip to 'apikey' mode. The
|
||||
// OpenRouter row selects its key; the generic link lands on the first option.
|
||||
const [apiKeyInitialEnv, setApiKeyInitialEnv] = useState<string | undefined>(undefined)
|
||||
const openKeyForm = (envKey?: string) => {
|
||||
setApiKeyInitialEnv(envKey)
|
||||
|
|
@ -459,9 +457,6 @@ export function Picker({ ctx }: { ctx: OnboardingContext }) {
|
|||
<div className="grid gap-2">
|
||||
<div className="grid max-h-[60dvh] gap-2 overflow-y-auto p-1">
|
||||
{featured ? <FeaturedProviderRow onSelect={select} provider={featured} /> : null}
|
||||
{/* Fireworks — preferred BYOK provider, second hero card after Nous.
|
||||
Routes into the API-key form preselecting the Fireworks key. */}
|
||||
<FeaturedFireworksRow onSelect={() => openKeyForm('FIREWORKS_API_KEY')} />
|
||||
{showRest ? (
|
||||
<>
|
||||
{rest.map(p => (
|
||||
|
|
|
|||
|
|
@ -62,34 +62,6 @@ export function FeaturedProviderRow({
|
|||
)
|
||||
}
|
||||
|
||||
// Fireworks is the preferred BYOK provider — a second hero card directly below
|
||||
// Nous Portal. It isn't an OAuth provider (no sign-in flow), so it takes a bare
|
||||
// onSelect that routes into the API-key form rather than an OAuthProvider.
|
||||
export function FeaturedFireworksRow({ onSelect }: { onSelect: () => void }) {
|
||||
const { t } = useI18n()
|
||||
|
||||
return (
|
||||
<button
|
||||
className="group relative flex w-full items-center justify-between gap-4 rounded-[8px] bg-primary/[0.06] px-3 py-2.5 text-left transition-colors hover:bg-primary/10"
|
||||
onClick={onSelect}
|
||||
type="button"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<img alt="" className="size-5 shrink-0" src={assetPath('fireworks.svg')} />
|
||||
<span className="text-[length:var(--conversation-text-font-size)] font-semibold">Fireworks AI</span>
|
||||
<span className="inline-flex items-center gap-1.5 bg-[#6720FF] px-2 py-0.5 text-[0.64rem] font-semibold uppercase tracking-[0.16em] text-white">
|
||||
<img alt="" aria-hidden="true" className="size-2.5 shrink-0" src={assetPath('fireworks-white.svg')} />
|
||||
{t.onboarding.recommended}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-xs leading-5 text-muted-foreground">{t.onboarding.fireworksPitch}</p>
|
||||
</div>
|
||||
<ChevronRight className="size-4 shrink-0 text-primary transition group-hover:translate-x-0.5" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
function ConnectedTag() {
|
||||
const { t } = useI18n()
|
||||
|
||||
|
|
|
|||
|
|
@ -1928,12 +1928,11 @@ export const en: Translations = {
|
|||
recommended: 'Recommended',
|
||||
connected: 'Connected',
|
||||
featuredPitch: 'One subscription, 300+ frontier models — the recommended way to run Hermes',
|
||||
fireworksPitch: 'Fastest inference for production AI — 300+ models, one API, deploy in a click',
|
||||
openRouterPitch: 'One key, hundreds of models — a solid default',
|
||||
apiKeyOptions: {
|
||||
fireworks: {
|
||||
short: 'fastest inference',
|
||||
description: 'Direct access to 300+ open & proprietary models on Fireworks AI — one API key, production speed.'
|
||||
short: 'direct model API',
|
||||
description: 'Direct access to models hosted by Fireworks AI.'
|
||||
},
|
||||
openrouter: {
|
||||
short: 'one key, many models',
|
||||
|
|
|
|||
|
|
@ -1873,12 +1873,11 @@ export const ja = defineLocale({
|
|||
recommended: '推奨',
|
||||
connected: '接続済み',
|
||||
featuredPitch: '1 つのサブスクリプションで 300 以上の最先端モデル — Hermes を実行するための推奨方法',
|
||||
fireworksPitch: 'プロダクション AI 向けの最速推論 — 300 以上のモデル、1 つの API、ワンクリックでデプロイ',
|
||||
openRouterPitch: '1 つのキーで数百のモデル — 堅実なデフォルト',
|
||||
apiKeyOptions: {
|
||||
fireworks: {
|
||||
short: '最速の推論',
|
||||
description: 'Fireworks AI で 300 以上のオープン/プロプライエタリモデルに直接アクセス — 1 つの API キー、プロダクション速度。'
|
||||
short: 'モデル API に直接接続',
|
||||
description: 'Fireworks AI がホストするモデルに直接アクセスします。'
|
||||
},
|
||||
openrouter: {
|
||||
short: '1 つのキーで多くのモデル',
|
||||
|
|
|
|||
|
|
@ -1582,7 +1582,6 @@ export interface Translations {
|
|||
recommended: string
|
||||
connected: string
|
||||
featuredPitch: string
|
||||
fireworksPitch: string
|
||||
openRouterPitch: string
|
||||
apiKeyOptions: Record<string, { short: string; description: string }>
|
||||
backToSignIn: string
|
||||
|
|
|
|||
|
|
@ -1817,10 +1817,9 @@ export const zhHant = defineLocale({
|
|||
recommended: '建議',
|
||||
connected: '已連線',
|
||||
featuredPitch: '一個訂閱,300+ 前沿模型 — 執行 Hermes 的建議方式',
|
||||
fireworksPitch: '面向生產 AI 的最快推理 — 300+ 模型,一個 API,一鍵部署',
|
||||
openRouterPitch: '一個金鑰,數百個模型 — 穩定的預設選擇',
|
||||
apiKeyOptions: {
|
||||
fireworks: { short: '最快推理', description: '透過 Fireworks AI 直接存取 300+ 開源與專有模型 — 一個 API 金鑰,生產級速度。' },
|
||||
fireworks: { short: '直接模型 API', description: '直接存取 Fireworks AI 託管的模型。' },
|
||||
openrouter: { short: '一個金鑰,多個模型', description: '用一個金鑰存取數百個模型。適合新安裝的預設選擇。' },
|
||||
openai: { short: 'GPT 等級模型', description: '直接存取 OpenAI 模型。' },
|
||||
gemini: { short: 'Gemini 模型', description: '直接存取 Google Gemini 模型。' },
|
||||
|
|
|
|||
|
|
@ -2100,10 +2100,9 @@ export const zh: Translations = {
|
|||
recommended: '推荐',
|
||||
connected: '已连接',
|
||||
featuredPitch: '一个订阅,300+ 前沿模型 — 运行 Hermes 的推荐方式',
|
||||
fireworksPitch: '面向生产 AI 的最快推理 — 300+ 模型,一个 API,一键部署',
|
||||
openRouterPitch: '一个密钥,数百个模型 — 稳妥的默认选择',
|
||||
apiKeyOptions: {
|
||||
fireworks: { short: '最快推理', description: '通过 Fireworks AI 直接访问 300+ 开源与专有模型 — 一个 API 密钥,生产级速度。' },
|
||||
fireworks: { short: '直接模型 API', description: '直接访问 Fireworks AI 托管的模型。' },
|
||||
openrouter: { short: '一个密钥,多个模型', description: '用一个密钥访问数百个模型。适合新安装的默认选择。' },
|
||||
openai: { short: 'GPT 级模型', description: '直接访问 OpenAI 模型。' },
|
||||
gemini: { short: 'Gemini 模型', description: '直接访问 Google Gemini 模型。' },
|
||||
|
|
|
|||
|
|
@ -3543,14 +3543,6 @@ OPTIONAL_ENV_VARS = {
|
|||
"category": "provider",
|
||||
"advanced": True,
|
||||
},
|
||||
"FIREWORKS_BASE_URL": {
|
||||
"description": "Fireworks AI base URL override",
|
||||
"prompt": "Fireworks AI base URL (leave empty for default)",
|
||||
"url": None,
|
||||
"password": False,
|
||||
"category": "provider",
|
||||
"advanced": True,
|
||||
},
|
||||
"MINIMAX_API_KEY": {
|
||||
"description": "MiniMax API key (international)",
|
||||
"prompt": "MiniMax API key",
|
||||
|
|
|
|||
|
|
@ -1051,9 +1051,6 @@ class ProviderEntry(NamedTuple):
|
|||
|
||||
CANONICAL_PROVIDERS: list[ProviderEntry] = [
|
||||
ProviderEntry("nous", "Nous Portal", "Nous Portal (Everything your agent needs, 300+ models with bundled tool use)"),
|
||||
# Fireworks AI — preferred BYOK inference partner. Sits directly below Nous
|
||||
# Portal (Nous's own provider stays first); everything else follows.
|
||||
ProviderEntry("fireworks", "Fireworks AI", "Fireworks AI (Fastest inference for production AI, 300+ models, one API key)"),
|
||||
ProviderEntry("openrouter", "OpenRouter", "OpenRouter (Pay-per-use API aggregator)"),
|
||||
ProviderEntry("moa", "Mixture of Agents", "Mixture of Agents (named presets; aggregator acts after reference models)"),
|
||||
ProviderEntry("novita", "NovitaAI", "NovitaAI (Cloud: Model API, Agent Sandbox, GPU Cloud)"),
|
||||
|
|
@ -1083,6 +1080,7 @@ CANONICAL_PROVIDERS: list[ProviderEntry] = [
|
|||
ProviderEntry("ollama-cloud", "Ollama Cloud", "Ollama Cloud (Cloud-hosted open models, ollama.com)"),
|
||||
ProviderEntry("arcee", "Arcee AI", "Arcee AI (Trinity models, direct API)"),
|
||||
ProviderEntry("gmi", "GMI Cloud", "GMI Cloud (Multi-model direct API)"),
|
||||
ProviderEntry("fireworks", "Fireworks AI", "Fireworks AI (OpenAI-compatible direct model API)"),
|
||||
ProviderEntry("kilocode", "Kilo Code", "Kilo Code (Kilo Gateway API)"),
|
||||
ProviderEntry("opencode-zen", "OpenCode Zen", "OpenCode Zen (Curated models, pay-as-you-go)"),
|
||||
ProviderEntry("opencode-go", "OpenCode Go", "OpenCode Go (Open models subscription)"),
|
||||
|
|
|
|||
|
|
@ -200,7 +200,6 @@ HERMES_OVERLAYS: Dict[str, HermesOverlay] = {
|
|||
transport="openai_chat",
|
||||
extra_env_vars=("FIREWORKS_API_KEY",),
|
||||
base_url_override="https://api.fireworks.ai/inference/v1",
|
||||
base_url_env_var="FIREWORKS_BASE_URL",
|
||||
),
|
||||
"ollama-cloud": HermesOverlay(
|
||||
transport="openai_chat",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
"""Fireworks AI provider profile.
|
||||
|
||||
Fireworks AI serves fast, production-grade inference for open and proprietary
|
||||
models through an OpenAI-compatible chat-completions endpoint. It is a bundled,
|
||||
preferred BYOK provider — users paste a Fireworks API key and go.
|
||||
models through an OpenAI-compatible chat-completions endpoint.
|
||||
|
||||
Address models directly by their catalog ID, e.g.
|
||||
``accounts/fireworks/models/kimi-k2p6`` or ``accounts/fireworks/models/glm-5p2``.
|
||||
|
|
@ -18,19 +17,11 @@ fireworks = ProviderProfile(
|
|||
name="fireworks",
|
||||
aliases=("fireworks-ai", "fw"),
|
||||
display_name="Fireworks AI",
|
||||
description="Fireworks AI — fastest inference for production AI, 300+ models, one API key",
|
||||
description="Fireworks AI — OpenAI-compatible direct model API",
|
||||
signup_url="https://app.fireworks.ai/settings/users/api-keys",
|
||||
env_vars=("FIREWORKS_API_KEY", "FIREWORKS_BASE_URL"),
|
||||
env_vars=("FIREWORKS_API_KEY",),
|
||||
base_url="https://api.fireworks.ai/inference/v1",
|
||||
auth_type="api_key",
|
||||
# Attribution: lets Fireworks identify traffic originating from Hermes Agent
|
||||
# (partner/revenue attribution). Matches the canonical Hermes attribution
|
||||
# values in agent/auxiliary_client.py. Applied at client construction via the
|
||||
# generic profile.default_headers path in run_agent.py.
|
||||
default_headers={
|
||||
"HTTP-Referer": "https://hermes-agent.nousresearch.com",
|
||||
"X-Title": "Hermes Agent",
|
||||
},
|
||||
# Auxiliary model for cheap tasks (compaction, title generation, vision).
|
||||
# A standard pay-as-you-go catalog ``/models/`` ID.
|
||||
default_aux_model="accounts/fireworks/models/glm-5p2",
|
||||
|
|
@ -43,26 +34,3 @@ fireworks = ProviderProfile(
|
|||
)
|
||||
|
||||
register_provider(fireworks)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Future work (intentionally disabled for now — do not ship as customer-facing).
|
||||
#
|
||||
# A second Fireworks key tier authenticates against the same endpoint but can
|
||||
# only address managed router IDs (accounts/fireworks/routers/...), not raw
|
||||
# /models/ IDs, and cannot list the account catalog. Enabling it means detecting
|
||||
# the key prefix and swapping the picker catalog + aux model to routers. Kept
|
||||
# here (commented out) so the wiring is ready when we turn it on. Router IDs and
|
||||
# behavior track fw-ai/fireconnect setup-cli.
|
||||
#
|
||||
# _MANAGED_KEY_PREFIX = "fpk_"
|
||||
# _MANAGED_ROUTERS = (
|
||||
# "accounts/fireworks/routers/glm-latest", # default
|
||||
# "accounts/fireworks/routers/glm-fast-latest",
|
||||
# "accounts/fireworks/routers/kimi-latest",
|
||||
# "accounts/fireworks/routers/kimi-k2p6-turbo", # turbo tier — not a default
|
||||
# )
|
||||
#
|
||||
# def _is_managed_key(api_key: str) -> bool:
|
||||
# return bool(api_key) and api_key.startswith(_MANAGED_KEY_PREFIX)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ name: fireworks-provider
|
|||
kind: model-provider
|
||||
version: 1.0.0
|
||||
description: Fireworks AI — fast inference for open and proprietary models
|
||||
author: Nous Research
|
||||
author: Alex Jestin Taylor (@alex-fireworks) + Hermes Agent
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ AUTHOR_MAP = {
|
|||
"lord-dubious@users.noreply.github.com": "lord-dubious", # PR #58453 salvage (preserve static custom provider models declared as dict rows)
|
||||
"williamumu@users.noreply.github.com": "williamumu", # PR #31041 salvage (pairing: merge split legacy/new pairing store dirs at PairingStore init so approved users aren't re-prompted to pair)
|
||||
"jonathan@mintrx.com": "JAlmanzarMint", # PR #52688 salvage (vision: rasterize SVG / re-encode unsupported raster formats to PNG before embedding), folded into #57890
|
||||
"alex@fireworks.ai": "alex-fireworks", # PR #61182 salvage (Fireworks AI model-provider integration)
|
||||
"al3060388206@gmail.com": "ooiuuii", # PR #58466/#58377 salvage (redact: fireworks fw-/fpk_ prefixes; telegram: redact bot tokens out of transport error strings). Also PR #58433 salvage (codex: accept recorded final_text when app-server omits turn/completed) and PR #58472 salvage (gateway: cap proxy SSE line buffer at 16MiB).
|
||||
"Jigoooo@users.noreply.github.com": "Jigoooo", # PR #58474 salvage (auxiliary: fall back to token resolver when anthropic pool has no usable entry)
|
||||
"root@vmi3351581.contaboserver.net": "ostravajih", # PR #58374 salvage (poolside: coerce integer finish_reason and tool_call id to strings)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
"""Focused tests for Fireworks AI first-class provider wiring.
|
||||
|
||||
Fireworks is the bundled, preferred BYOK provider (sits right after Nous
|
||||
Portal). These tests pin the wiring that makes it a real provider — alias
|
||||
resolution through BOTH CLI resolvers, ordering, config/doctor/overlay
|
||||
registration, attribution headers, and credential/base-URL resolution — without
|
||||
These tests pin the wiring that makes Fireworks a real provider — alias
|
||||
resolution through both CLI resolvers, config/doctor/overlay registration,
|
||||
and credential/base-URL resolution — without
|
||||
any live network calls.
|
||||
"""
|
||||
|
||||
|
|
@ -48,21 +47,12 @@ class TestFireworksAliases:
|
|||
|
||||
|
||||
class TestFireworksOrdering:
|
||||
"""Product contract: Fireworks is the preferred provider and sits directly
|
||||
below Nous Portal (Nous's own provider stays first)."""
|
||||
"""Fireworks participates in the canonical provider catalog."""
|
||||
|
||||
def test_present_in_canonical_providers(self):
|
||||
slugs = [p.slug for p in CANONICAL_PROVIDERS]
|
||||
assert "fireworks" in slugs
|
||||
|
||||
def test_sits_immediately_after_nous(self):
|
||||
slugs = [p.slug for p in CANONICAL_PROVIDERS]
|
||||
assert slugs.index("fireworks") == slugs.index("nous") + 1, slugs[:4]
|
||||
|
||||
def test_ranks_ahead_of_the_other_byok_providers(self):
|
||||
slugs = [p.slug for p in CANONICAL_PROVIDERS]
|
||||
# Ahead of the generic aggregators / other direct providers.
|
||||
assert slugs.index("fireworks") < slugs.index("openrouter")
|
||||
|
||||
def test_has_a_label(self):
|
||||
assert _PROVIDER_LABELS.get("fireworks") == "Fireworks AI"
|
||||
|
|
@ -76,8 +66,7 @@ class TestFireworksConfigRegistry:
|
|||
assert OPTIONAL_ENV_VARS["FIREWORKS_API_KEY"]["category"] == "provider"
|
||||
assert OPTIONAL_ENV_VARS["FIREWORKS_API_KEY"]["password"] is True
|
||||
|
||||
assert "FIREWORKS_BASE_URL" in OPTIONAL_ENV_VARS
|
||||
assert OPTIONAL_ENV_VARS["FIREWORKS_BASE_URL"]["password"] is False
|
||||
assert "FIREWORKS_BASE_URL" not in OPTIONAL_ENV_VARS
|
||||
|
||||
|
||||
class TestFireworksOverlay:
|
||||
|
|
@ -88,7 +77,7 @@ class TestFireworksOverlay:
|
|||
overlay = HERMES_OVERLAYS["fireworks"]
|
||||
assert overlay.transport == "openai_chat"
|
||||
assert overlay.base_url_override == "https://api.fireworks.ai/inference/v1"
|
||||
assert overlay.base_url_env_var == "FIREWORKS_BASE_URL"
|
||||
assert not overlay.base_url_env_var
|
||||
assert not overlay.is_aggregator
|
||||
|
||||
|
||||
|
|
@ -153,16 +142,8 @@ class TestFireworksCredentials:
|
|||
assert creds["api_key"] == "fw_test_key"
|
||||
assert creds["base_url"] == "https://api.fireworks.ai/inference/v1"
|
||||
|
||||
def test_base_url_override_is_honored(self, monkeypatch):
|
||||
monkeypatch.setenv("FIREWORKS_API_KEY", "fw_test_key")
|
||||
monkeypatch.setenv("FIREWORKS_BASE_URL", "https://gateway.example.com/v1")
|
||||
creds = resolve_api_key_provider_credentials("fireworks")
|
||||
assert creds["base_url"] == "https://gateway.example.com/v1"
|
||||
|
||||
|
||||
class TestFireworksAuxiliary:
|
||||
"""resolve_provider_client wires the BYOK key, PAYG-safe aux model, and the
|
||||
partner-attribution headers through to the OpenAI client."""
|
||||
"""resolve_provider_client wires the BYOK key and PAYG-safe aux model."""
|
||||
|
||||
def _resolve(self, name):
|
||||
from unittest.mock import patch
|
||||
|
|
@ -174,13 +155,13 @@ class TestFireworksAuxiliary:
|
|||
client, model = resolve_provider_client(name)
|
||||
return client, model, mock_openai.call_args.kwargs
|
||||
|
||||
def test_client_carries_attribution_headers(self, monkeypatch):
|
||||
def test_client_has_no_partner_attribution_headers(self, monkeypatch):
|
||||
monkeypatch.setenv("FIREWORKS_API_KEY", "fw_test_key")
|
||||
client, model, kwargs = self._resolve("fireworks")
|
||||
assert client is not None
|
||||
headers = kwargs.get("default_headers", {})
|
||||
assert headers.get("HTTP-Referer") == "https://hermes-agent.nousresearch.com"
|
||||
assert headers.get("X-Title") == "Hermes Agent"
|
||||
assert "HTTP-Referer" not in headers
|
||||
assert "X-Title" not in headers
|
||||
assert kwargs["base_url"] == "https://api.fireworks.ai/inference/v1"
|
||||
|
||||
def test_aux_model_is_payg_safe(self, monkeypatch):
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
"""Unit tests for the Fireworks AI provider profile.
|
||||
|
||||
Pins the profile's contract without going live: identity, attribution headers
|
||||
(so Fireworks can attribute Hermes traffic for the partnership), alias
|
||||
registration, and the pay-as-you-go model defaults (direct catalog ``/models/``
|
||||
Pins the profile's contract without going live: identity, alias registration,
|
||||
and the pay-as-you-go model defaults (direct catalog ``/models/``
|
||||
IDs, not the router-only tier).
|
||||
"""
|
||||
|
||||
|
|
@ -30,7 +29,7 @@ class TestFireworksIdentity:
|
|||
assert p.auth_type == "api_key"
|
||||
assert p.base_url == "https://api.fireworks.ai/inference/v1"
|
||||
assert "FIREWORKS_API_KEY" in p.env_vars
|
||||
assert "FIREWORKS_BASE_URL" in p.env_vars
|
||||
assert "FIREWORKS_BASE_URL" not in p.env_vars
|
||||
|
||||
def test_display_metadata_present(self, fireworks_profile):
|
||||
# Prominence copy is surfaced in the picker; keep it non-empty rather
|
||||
|
|
@ -40,13 +39,10 @@ class TestFireworksIdentity:
|
|||
assert fireworks_profile.signup_url.startswith("https://")
|
||||
|
||||
|
||||
class TestFireworksAttributionHeaders:
|
||||
"""The profile carries the partner-attribution headers Fireworks captures."""
|
||||
|
||||
def test_referer_and_title_headers(self, fireworks_profile):
|
||||
headers = fireworks_profile.default_headers
|
||||
assert headers.get("HTTP-Referer") == "https://hermes-agent.nousresearch.com"
|
||||
assert headers.get("X-Title") == "Hermes Agent"
|
||||
class TestFireworksHeaders:
|
||||
def test_no_partner_attribution_headers(self, fireworks_profile):
|
||||
assert "HTTP-Referer" not in fireworks_profile.default_headers
|
||||
assert "X-Title" not in fireworks_profile.default_headers
|
||||
|
||||
|
||||
class TestFireworksAliases:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Opt-in only:
|
|||
|
||||
Unlike a bare OpenAI() client pointed at the endpoint, this drives Hermes'
|
||||
own provider resolution — ``resolve_provider_client('fireworks')`` — so it
|
||||
verifies the auth/config/base-URL/attribution-header/aux-model wiring that the
|
||||
verifies the auth/config/base-URL/aux-model wiring that the
|
||||
bundled provider actually ships, then makes a real call through that client.
|
||||
"""
|
||||
|
||||
|
|
@ -51,8 +51,6 @@ def test_fireworks_basic_chat_through_runtime():
|
|||
response = client.chat.completions.create(
|
||||
model=model,
|
||||
messages=[{"role": "user", "content": "Say exactly the word 'pong' and nothing else."}],
|
||||
max_tokens=32,
|
||||
temperature=0.0,
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ Good defaults:
|
|||
| **OpenAI Codex** | ChatGPT OAuth, uses Codex models | Device code auth via `hermes model` |
|
||||
| **Anthropic** | Claude models directly — Max plan + extra usage credits (OAuth), or API key for pay-per-token | `hermes model` → OAuth login (requires Max + extra credits), or an Anthropic API key |
|
||||
| **OpenRouter** | Multi-provider routing across many models | Enter your API key |
|
||||
| **Fireworks AI** | Direct OpenAI-compatible model API | Set `FIREWORKS_API_KEY` |
|
||||
| **Z.AI** | GLM / Zhipu-hosted models | Set `GLM_API_KEY` / `ZAI_API_KEY` (also accepts `Z_AI_API_KEY`) |
|
||||
| **Kimi / Moonshot** | Moonshot-hosted coding and chat models | Set `KIMI_API_KEY` (or the Kimi-Coding-specific `KIMI_CODING_API_KEY`) |
|
||||
| **Kimi / Moonshot China** | China-region Moonshot endpoint | Set `KIMI_CN_API_KEY` |
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ You need at least one way to connect to an LLM. Use `hermes model` to switch pro
|
|||
| **GitHub Copilot ACP** | `hermes model` (spawns local `copilot --acp --stdio`) |
|
||||
| **Anthropic** | `hermes model` (Claude Max + extra usage credits via OAuth; also supports Anthropic API key or manual setup-token — see note below) |
|
||||
| **OpenRouter** | `OPENROUTER_API_KEY` in `~/.hermes/.env` |
|
||||
| **Fireworks AI** | `FIREWORKS_API_KEY` in `~/.hermes/.env` (provider: `fireworks`; aliases: `fireworks-ai`, `fw`) |
|
||||
| **NovitaAI** | `NOVITA_API_KEY` in `~/.hermes/.env` (provider: `novita`, 200+ models, Model API, Agent Sandbox, GPU Cloud) |
|
||||
| **z.ai / GLM** | `GLM_API_KEY` in `~/.hermes/.env` (provider: `zai`) |
|
||||
| **Kimi / Moonshot** | `KIMI_API_KEY` in `~/.hermes/.env` (provider: `kimi-coding`) |
|
||||
|
|
@ -214,6 +215,10 @@ model:
|
|||
These providers have built-in support with dedicated provider IDs. Set the API key and use `--provider` to select:
|
||||
|
||||
```bash
|
||||
# Fireworks AI
|
||||
hermes chat --provider fireworks --model accounts/fireworks/models/kimi-k2p6
|
||||
# Requires: FIREWORKS_API_KEY in ~/.hermes/.env
|
||||
|
||||
# NovitaAI Model API
|
||||
hermes chat --provider novita --model moonshotai/kimi-k2.5
|
||||
# Requires: NOVITA_API_KEY in ~/.hermes/.env
|
||||
|
|
@ -260,6 +265,8 @@ hermes chat --provider gmi --model zai-org/GLM-5.1-FP8
|
|||
# Requires: GMI_API_KEY in ~/.hermes/.env
|
||||
```
|
||||
|
||||
Fireworks uses its native slash-form catalog IDs, such as `accounts/fireworks/models/kimi-k2p6`. Run `hermes model`, choose **Fireworks AI**, and select from the live catalog or enter another Fireworks model ID. The default endpoint is `https://api.fireworks.ai/inference/v1`; configure a different endpoint through `model.base_url` in `config.yaml`, not `.env`.
|
||||
|
||||
Or set the provider permanently in `config.yaml`:
|
||||
```yaml
|
||||
model:
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ Hermes reads environment variables from the process environment and, for user-ma
|
|||
|----------|-------------|
|
||||
| `OPENROUTER_API_KEY` | OpenRouter API key (recommended for flexibility) |
|
||||
| `OPENROUTER_BASE_URL` | Override the OpenRouter-compatible base URL |
|
||||
| `FIREWORKS_API_KEY` | Fireworks AI API key ([app.fireworks.ai](https://app.fireworks.ai/settings/users/api-keys)). Configure endpoint overrides with `model.base_url` in `config.yaml`. |
|
||||
| `HERMES_OPENROUTER_CACHE` | Enable OpenRouter response caching (`1`/`true`/`yes`/`on`). Overrides `openrouter.response_cache` in config.yaml. See [Response Caching](https://openrouter.ai/docs/guides/features/response-caching). |
|
||||
| `HERMES_OPENROUTER_CACHE_TTL` | Cache TTL in seconds (1-86400). Overrides `openrouter.response_cache_ttl` in config.yaml. |
|
||||
| `NOUS_BASE_URL` | Override Nous Portal base URL (rarely needed; development/testing only) |
|
||||
|
|
|
|||
|
|
@ -1170,7 +1170,7 @@ These options apply to **auxiliary task configs** (`auxiliary:`, `compression:`)
|
|||
| `"xai-oauth"` | Force xAI Grok OAuth (browser login for SuperGrok or X Premium+ subscribers, no API key). Same OAuth token covers chat, TTS, image, video, and transcription. | `hermes model` → xAI Grok OAuth (SuperGrok / Premium+) |
|
||||
| `"main"` | Use your active custom/main endpoint. This can come from `OPENAI_BASE_URL` + `OPENAI_API_KEY` or from a custom endpoint saved via `hermes model` / `config.yaml`. Works with OpenAI, local models, or any OpenAI-compatible API. **Auxiliary tasks only — not valid for `model.provider`.** | Custom endpoint credentials + base URL |
|
||||
|
||||
Direct API-key providers from the main provider catalog also work here when you want side tasks to bypass your default router. `gmi` is valid once `GMI_API_KEY` is configured:
|
||||
Direct API-key providers from the main provider catalog also work here when you want side tasks to bypass your default router. For example, `gmi` is valid once `GMI_API_KEY` is configured, and `fireworks` is valid once `FIREWORKS_API_KEY` is configured:
|
||||
|
||||
```yaml
|
||||
auxiliary:
|
||||
|
|
@ -1179,7 +1179,7 @@ auxiliary:
|
|||
model: "anthropic/claude-opus-4.6"
|
||||
```
|
||||
|
||||
For GMI auxiliary routing, use the exact model ID returned by GMI's `/v1/models` endpoint.
|
||||
For GMI auxiliary routing, use the exact model ID returned by GMI's `/v1/models` endpoint. Fireworks model IDs use the provider's native slash form, for example `accounts/fireworks/models/glm-5p2`.
|
||||
|
||||
### Common Setups
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue