mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-28 07:19:08 +01:00
Merge remote-tracking branch 'origin/feat/kimi-3-agent-networks' into feature/kimi-3-agent-networks-dns-warmup
# Conflicts: # e2e/agentnetwork/chat_test.go # e2e/agentnetwork/guardrail_test.go
This commit is contained in:
@@ -51,6 +51,9 @@ jobs:
|
|||||||
# token (and URL, for gateways) is unset, so partial coverage is fine.
|
# token (and URL, for gateways) is unset, so partial coverage is fine.
|
||||||
OPENAI_TOKEN: ${{ secrets.E2E_OPENAI_TOKEN }}
|
OPENAI_TOKEN: ${{ secrets.E2E_OPENAI_TOKEN }}
|
||||||
ANTHROPIC_TOKEN: ${{ secrets.E2E_ANTHROPIC_TOKEN }}
|
ANTHROPIC_TOKEN: ${{ secrets.E2E_ANTHROPIC_TOKEN }}
|
||||||
|
# Moonshot AI platform key (platform.kimi.ai); drives both Kimi wire
|
||||||
|
# shapes (OpenAI /v1 and Anthropic /anthropic) through kimi_api.
|
||||||
|
KIMI_TOKEN: ${{ secrets.E2E_KIMI_TOKEN }}
|
||||||
VERCEL_URL: ${{ secrets.E2E_VERCEL_URL }}
|
VERCEL_URL: ${{ secrets.E2E_VERCEL_URL }}
|
||||||
VERCEL_TOKEN: ${{ secrets.E2E_VERCEL_TOKEN }}
|
VERCEL_TOKEN: ${{ secrets.E2E_VERCEL_TOKEN }}
|
||||||
OPENROUTER_URL: ${{ secrets.E2E_OPENROUTER_URL }}
|
OPENROUTER_URL: ${{ secrets.E2E_OPENROUTER_URL }}
|
||||||
|
|||||||
@@ -39,6 +39,21 @@ func availableProviders() []providerCase {
|
|||||||
if k := os.Getenv("ANTHROPIC_TOKEN"); k != "" {
|
if k := os.Getenv("ANTHROPIC_TOKEN"); k != "" {
|
||||||
ps = append(ps, providerCase{name: "anthropic", catalogID: "anthropic_api", upstream: "https://api.anthropic.com", apiKey: k, model: "claude-haiku-4-5", kind: harness.WireMessages})
|
ps = append(ps, providerCase{name: "anthropic", catalogID: "anthropic_api", upstream: "https://api.anthropic.com", apiKey: k, model: "claude-haiku-4-5", kind: harness.WireMessages})
|
||||||
}
|
}
|
||||||
|
if k := os.Getenv("KIMI_TOKEN"); k != "" {
|
||||||
|
// Kimi (Moonshot AI) serves two body shapes from the same key: OpenAI
|
||||||
|
// Chat Completions on the bare host (/v1/...) and the Anthropic
|
||||||
|
// Messages API under the /anthropic path prefix (the endpoint
|
||||||
|
// Moonshot's Claude Code guide uses). The platform serves this
|
||||||
|
// account exactly ONE model — kimi-k3 (kimi-k2-thinking and even
|
||||||
|
// kimi-latest return resource_not_found_error on both surfaces) — so
|
||||||
|
// two concurrent provider records would claim the same model and
|
||||||
|
// route ambiguously. Run the Anthropic shape, the flagship Claude
|
||||||
|
// Code path; the OpenAI wire shape is covered live by the other
|
||||||
|
// chat-shaped matrix providers, and Kimi-over-chat passed with
|
||||||
|
// kimi-k3 before the single-model constraint surfaced (run #73 on
|
||||||
|
// the kimi feature branch).
|
||||||
|
ps = append(ps, providerCase{name: "kimi", catalogID: "kimi_api", upstream: "https://api.moonshot.ai/anthropic", apiKey: k, model: "kimi-k3", kind: harness.WireMessages})
|
||||||
|
}
|
||||||
if k, u := os.Getenv("VERCEL_TOKEN"), os.Getenv("VERCEL_URL"); k != "" && u != "" {
|
if k, u := os.Getenv("VERCEL_TOKEN"), os.Getenv("VERCEL_URL"); k != "" && u != "" {
|
||||||
ps = append(ps, providerCase{name: "vercel", catalogID: "vercel_ai_gateway", upstream: u, apiKey: k, model: "openai/gpt-4o-mini", kind: harness.WireChat})
|
ps = append(ps, providerCase{name: "vercel", catalogID: "vercel_ai_gateway", upstream: u, apiKey: k, model: "openai/gpt-4o-mini", kind: harness.WireChat})
|
||||||
}
|
}
|
||||||
@@ -84,25 +99,46 @@ func availableProviders() []providerCase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bedrock: path-routed, bearer auth. Model is a cross-region inference
|
// Bedrock: path-routed, bearer auth. Model is the FULL cross-region
|
||||||
// profile id (distinct string from the first-party Anthropic case).
|
// inference-profile id exactly as AWS issues it — region-family prefix
|
||||||
|
// plus the date/version suffix. A bare or wrong-region id makes Bedrock
|
||||||
|
// reject the request with "The provided model identifier is invalid"
|
||||||
|
// before any inference runs. The proxy normalizes this id to the catalog
|
||||||
|
// key (anthropic.claude-haiku-4-5) for routing/pricing/allowlists.
|
||||||
|
// Defaults pair eu-central-1 with the eu.* profile; AWS_REGION overrides
|
||||||
|
// the region and the prefix follows its family.
|
||||||
if k := os.Getenv("AWS_BEARER_TOKEN_BEDROCK"); k != "" {
|
if k := os.Getenv("AWS_BEARER_TOKEN_BEDROCK"); k != "" {
|
||||||
region := os.Getenv("AWS_REGION")
|
region := os.Getenv("AWS_REGION")
|
||||||
if region == "" {
|
if region == "" {
|
||||||
region = "us-east-1"
|
region = "eu-central-1"
|
||||||
}
|
}
|
||||||
// A valid Bedrock inference-profile id (region prefix + date + version),
|
// A valid Bedrock inference-profile id (region prefix + date + version),
|
||||||
// overridable per account. `global.` profiles can be invoked from any
|
// overridable per account via AWS_BEDROCK_MODEL (e.g. a `global.`
|
||||||
// region; set AWS_BEDROCK_MODEL to match the enabled profile for the token.
|
// profile, invokable from any region). The default derives the
|
||||||
|
// region-family prefix from the configured region.
|
||||||
model := os.Getenv("AWS_BEDROCK_MODEL")
|
model := os.Getenv("AWS_BEDROCK_MODEL")
|
||||||
if model == "" {
|
if model == "" {
|
||||||
model = "global.anthropic.claude-haiku-4-5-20251001-v1:0"
|
model = bedrockProfilePrefix(region) + ".anthropic.claude-haiku-4-5-20251001-v1:0"
|
||||||
}
|
}
|
||||||
ps = append(ps, providerCase{name: "bedrock", catalogID: "bedrock_api", upstream: "https://bedrock-runtime." + region + ".amazonaws.com", apiKey: k, model: model, kind: harness.WireBedrock})
|
ps = append(ps, providerCase{name: "bedrock", catalogID: "bedrock_api", upstream: "https://bedrock-runtime." + region + ".amazonaws.com", apiKey: k, model: model, kind: harness.WireBedrock})
|
||||||
}
|
}
|
||||||
return ps
|
return ps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bedrockProfilePrefix maps an AWS region to its cross-region inference
|
||||||
|
// profile prefix: us-east-1 -> us, eu-central-1 -> eu, ap-southeast-1 -> apac,
|
||||||
|
// us-gov-west-1 -> us-gov.
|
||||||
|
func bedrockProfilePrefix(region string) string {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(region, "us-gov-"):
|
||||||
|
return "us-gov"
|
||||||
|
case strings.HasPrefix(region, "ap-"):
|
||||||
|
return "apac"
|
||||||
|
default:
|
||||||
|
return strings.SplitN(region, "-", 2)[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// providerRequest builds a create request for a matrix provider: enabled, with
|
// providerRequest builds a create request for a matrix provider: enabled, with
|
||||||
// a uniquely-priced model for body-routed providers and none for the
|
// a uniquely-priced model for body-routed providers and none for the
|
||||||
// path-routed Vertex (whose model lives in the request path).
|
// path-routed Vertex (whose model lives in the request path).
|
||||||
|
|||||||
@@ -17,8 +17,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// bedrockRegionPrefixes and bedrockVersionSuffix mirror the proxy's Bedrock
|
// bedrockRegionPrefixes and bedrockVersionSuffix mirror the proxy's Bedrock
|
||||||
// model normalization (region/inference-profile prefix + version suffix) so the
|
// model normalization (llm.NormalizeBedrockModel, not importable from here —
|
||||||
// provider is registered under the same catalog key the router matches against.
|
// proxy/internal): region/inference-profile prefix + version suffix are
|
||||||
|
// stripped so the provider is registered under the same catalog key the
|
||||||
|
// router and guardrail allowlist compare against.
|
||||||
var (
|
var (
|
||||||
bedrockRegionPrefixes = []string{"us.", "eu.", "apac.", "global."}
|
bedrockRegionPrefixes = []string{"us.", "eu.", "apac.", "global."}
|
||||||
bedrockVersionSuffix = regexp.MustCompile(`-(\d{8}-)?v\d+(:\d+)?$`)
|
bedrockVersionSuffix = regexp.MustCompile(`-(\d{8}-)?v\d+(:\d+)?$`)
|
||||||
@@ -52,7 +54,9 @@ func catalogModel(pc providerCase) string {
|
|||||||
func disallowedModel(pc providerCase) string {
|
func disallowedModel(pc providerCase) string {
|
||||||
switch pc.kind {
|
switch pc.kind {
|
||||||
case harness.WireBedrock:
|
case harness.WireBedrock:
|
||||||
return "us.anthropic.claude-opus-4-8"
|
// Same profile prefix as the allowed model so only the model name
|
||||||
|
// differs; the guardrail must deny it before it reaches AWS.
|
||||||
|
return strings.SplitN(pc.model, ".", 2)[0] + ".anthropic.claude-opus-4-8"
|
||||||
case harness.WireVertex:
|
case harness.WireVertex:
|
||||||
return "claude-opus-4-8@20250101"
|
return "claude-opus-4-8@20250101"
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -56,6 +56,11 @@ func StartClient(ctx context.Context, c *Combined, setupKey string) (*Client, er
|
|||||||
// Match the proxy: the combined relay is WebSocket-only, so the
|
// Match the proxy: the combined relay is WebSocket-only, so the
|
||||||
// client must use WS transport to keep a stable relay link to it.
|
// client must use WS transport to keep a stable relay link to it.
|
||||||
"NB_RELAY_TRANSPORT": "ws",
|
"NB_RELAY_TRANSPORT": "ws",
|
||||||
|
// Lazy connections defer the peer dial until traffic flows, which
|
||||||
|
// leaves `netbird status` at "0/N Connected" and starves
|
||||||
|
// WaitProxyPeer. Force eager connections so the proxy peer shows
|
||||||
|
// Connected as soon as the tunnel is up.
|
||||||
|
"NB_LAZY_CONN": "false",
|
||||||
},
|
},
|
||||||
HostConfigModifier: func(hc *container.HostConfig) {
|
HostConfigModifier: func(hc *container.HostConfig) {
|
||||||
hc.CapAdd = append(hc.CapAdd, "NET_ADMIN", "SYS_ADMIN", "SYS_RESOURCE")
|
hc.CapAdd = append(hc.CapAdd, "NET_ADMIN", "SYS_ADMIN", "SYS_RESOURCE")
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ func StartProxy(ctx context.Context, c *Combined, proxyToken string) (*Proxy, er
|
|||||||
// Management is plain HTTP in-cluster, so allow the proxy token to
|
// Management is plain HTTP in-cluster, so allow the proxy token to
|
||||||
// ride a non-TLS gRPC connection.
|
// ride a non-TLS gRPC connection.
|
||||||
"NB_PROXY_ALLOW_INSECURE": "true",
|
"NB_PROXY_ALLOW_INSECURE": "true",
|
||||||
|
// Keep the embedded overlay peer eager (see the client env note):
|
||||||
|
// lazy connections leave peers unconnected until traffic, which
|
||||||
|
// starves the harness's WaitProxyPeer.
|
||||||
|
"NB_LAZY_CONN": "false",
|
||||||
// The combined server multiplexes the relay over WebSocket on :8080
|
// The combined server multiplexes the relay over WebSocket on :8080
|
||||||
// (no QUIC listener). The proxy's embedded relay client defaults to
|
// (no QUIC listener). The proxy's embedded relay client defaults to
|
||||||
// QUIC, which fails here and flaps the relay link, churning the
|
// QUIC, which fails here and flaps the relay link, churning the
|
||||||
|
|||||||
@@ -420,6 +420,47 @@ var providers = []Provider{
|
|||||||
{ID: "mistral-embed", Label: "Mistral Embed", InputPer1k: 0.0001, OutputPer1k: 0, ContextWindow: 8192},
|
{ID: "mistral-embed", Label: "Mistral Embed", InputPer1k: 0.0001, OutputPer1k: 0, ContextWindow: 8192},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "kimi_api",
|
||||||
|
Kind: KindProvider,
|
||||||
|
Name: "Kimi (Moonshot AI) API",
|
||||||
|
Description: "Kimi K3 / K2 models via the Moonshot AI platform",
|
||||||
|
DefaultHost: "api.moonshot.ai",
|
||||||
|
AuthHeaderName: "Authorization",
|
||||||
|
AuthHeaderTemplate: "Bearer ${API_KEY}",
|
||||||
|
DefaultContentType: "application/json",
|
||||||
|
BrandColor: "#1A1A2E",
|
||||||
|
// ParserID empty on purpose: Moonshot serves two body shapes on
|
||||||
|
// the same host and key, and the proxy's URL sniffer dispatches
|
||||||
|
// both (same pattern as Bifrost). /v1/chat/completions matches
|
||||||
|
// OpenAIParser; the Anthropic-compatible endpoint the official
|
||||||
|
// Claude Code guide uses (/anthropic/v1/messages) contains
|
||||||
|
// "/v1/messages" and matches AnthropicParser. Pinning "openai"
|
||||||
|
// here would misparse the Claude Code path — the primary way
|
||||||
|
// teams consume Kimi for coding today. Both endpoints accept the
|
||||||
|
// same Moonshot key via Authorization: Bearer (Claude Code's
|
||||||
|
// ANTHROPIC_AUTH_TOKEN rides that header too).
|
||||||
|
//
|
||||||
|
// api.moonshot.ai is the international platform; mainland-China
|
||||||
|
// accounts live on api.moonshot.cn with separate billing —
|
||||||
|
// operators there override the host on the provider record. The
|
||||||
|
// kimi.com subscription coding endpoint (api.kimi.com/coding,
|
||||||
|
// model id "k3") is account-bound seat licensing rather than a
|
||||||
|
// meterable platform key, so it's deliberately not the default.
|
||||||
|
ParserID: "",
|
||||||
|
// Pricing per Moonshot's platform rates at K3 launch (July 2026):
|
||||||
|
// $3/$15 per MTok with $0.30 cached input, flat across the 1M-token
|
||||||
|
// window. kimi-k3 is the ONLY model the platform serves newer
|
||||||
|
// accounts — K2-era ids (kimi-k2-thinking) and even the kimi-latest
|
||||||
|
// alias return resource_not_found_error, verified live 2026-07-21 —
|
||||||
|
// so it's the only catalog entry. Grandfathered accounts with K2
|
||||||
|
// access can still type those ids on the provider's model rows.
|
||||||
|
// The consumer app's "K3 Swarm Max" mode is not an API SKU, so it
|
||||||
|
// doesn't appear here.
|
||||||
|
Models: []Model{
|
||||||
|
{ID: "kimi-k3", Label: "Kimi K3", InputPer1k: 0.003, OutputPer1k: 0.015, ContextWindow: 1000000},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
ID: "litellm_proxy",
|
ID: "litellm_proxy",
|
||||||
Kind: KindGateway,
|
Kind: KindGateway,
|
||||||
|
|||||||
@@ -161,6 +161,16 @@ openai:
|
|||||||
input_per_1k: 0.0001
|
input_per_1k: 0.0001
|
||||||
output_per_1k: 0
|
output_per_1k: 0
|
||||||
|
|
||||||
|
# Kimi / Moonshot AI (kimi_api) — OpenAI-compatible /v1 endpoint. Moonshot
|
||||||
|
# reports cache hits OpenAI-style when present; cached input is 10% of
|
||||||
|
# input ($0.30 vs $3.00 per MTok). kimi-k3 is the only model the platform
|
||||||
|
# serves newer accounts (K2-era ids and kimi-latest 404), matching the
|
||||||
|
# management catalog.
|
||||||
|
kimi-k3:
|
||||||
|
input_per_1k: 0.003
|
||||||
|
output_per_1k: 0.015
|
||||||
|
cached_input_per_1k: 0.0003
|
||||||
|
|
||||||
anthropic:
|
anthropic:
|
||||||
# Claude 4.x family — cache reads ≈10% of input, cache writes ≈125% of input.
|
# Claude 4.x family — cache reads ≈10% of input, cache writes ≈125% of input.
|
||||||
# Pricing source: Anthropic's current published rates per million tokens,
|
# Pricing source: Anthropic's current published rates per million tokens,
|
||||||
@@ -206,6 +216,20 @@ anthropic:
|
|||||||
cache_read_per_1k: 0.0001
|
cache_read_per_1k: 0.0001
|
||||||
cache_creation_per_1k: 0.00125
|
cache_creation_per_1k: 0.00125
|
||||||
|
|
||||||
|
# Kimi / Moonshot AI (kimi_api) via the Anthropic-compatible endpoint
|
||||||
|
# (/anthropic/v1/messages — the official Claude Code setup). Same rates
|
||||||
|
# as the OpenAI-shape entry above. "kimi-k3[1m]" is the model id some
|
||||||
|
# Claude Code guides set for the 1M-context alias; priced identically so
|
||||||
|
# cost metering doesn't silently skip those requests.
|
||||||
|
kimi-k3:
|
||||||
|
input_per_1k: 0.003
|
||||||
|
output_per_1k: 0.015
|
||||||
|
cache_read_per_1k: 0.0003
|
||||||
|
"kimi-k3[1m]":
|
||||||
|
input_per_1k: 0.003
|
||||||
|
output_per_1k: 0.015
|
||||||
|
cache_read_per_1k: 0.0003
|
||||||
|
|
||||||
bedrock:
|
bedrock:
|
||||||
# AWS Bedrock model ids, normalised by the request parser (cross-region
|
# AWS Bedrock model ids, normalised by the request parser (cross-region
|
||||||
# inference-profile prefix + version/throughput suffix stripped), e.g.
|
# inference-profile prefix + version/throughput suffix stripped), e.g.
|
||||||
|
|||||||
Reference in New Issue
Block a user