Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions R/NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export(CacheManager)
export(DeepSeekProcessor)
export(GeminiProcessor)
export(GrokProcessor)
export(KimiProcessor)
export(MinimaxProcessor)
export(OpenAIProcessor)
export(OpenRouterProcessor)
Expand Down
16 changes: 16 additions & 0 deletions R/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

## 2.0.6 (2026-07-15)

### New Provider Support
* Added native support for **Kimi (Moonshot AI)** models through their
Anthropic-compatible Messages API:
- New `KimiProcessor` class and `process_kimi()` handler reusing the
`/v1/messages` endpoint with `x-api-key` + `anthropic-version`
authentication.
- `get_provider()` now recognizes `kimi-*` and `moonshot*` model prefixes
(e.g., `kimi-k2.7`, `kimi-k2.7[1m]`).
- Default endpoint `https://api.kimi.com/coding/v1/messages`; the API key is
read from the `KIMI_API_KEY` environment variable.
- Token usage is captured via `KimiProcessor$extract_usage()` (R) and the
optional `usage_sink` argument of `process_kimi()` (Python), matching the
normalized usage contract introduced in this release.
- Matching support added to the Python package (`process_kimi`, provider
registration, and default configuration).

### Reliability
* Unified validation for prompts, model identities, provider responses, cluster
identifiers, consensus metrics, base URLs, and cache payloads.
Expand Down
1 change: 1 addition & 0 deletions R/R/base_api_processor.R
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ BaseAPIProcessor <- R6::R6Class("BaseAPIProcessor",
zhipu = "Zhipu",
minimax = "MiniMax",
grok = "Grok",
kimi = "Kimi",
openrouter = "OpenRouter"
)
provider_label <- provider_names[[self$provider_name]]
Expand Down
1 change: 1 addition & 0 deletions R/R/get_model_response.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ get_builtin_provider_processors <- function() {
zhipu = process_zhipu,
minimax = process_minimax,
grok = process_grok,
kimi = process_kimi,
openrouter = process_openrouter
)
}
Expand Down
4 changes: 3 additions & 1 deletion R/R/get_provider.R
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"stepfun" = "^step-",
"zhipu" = "^(glm-|chatglm)",
"minimax" = "^minimax-",
"grok" = "^grok-"
"grok" = "^grok-",
"kimi" = "^(kimi-|moonshot)"
)

#' Determine provider from model name
Expand All @@ -30,6 +31,7 @@
#' \item Zhipu: glm-*, chatglm* (e.g., 'glm-5.1', 'glm-5-turbo')
#' \item MiniMax: minimax-* (e.g., 'MiniMax-M2.7', 'MiniMax-M2.5')
#' \item Grok: grok-* (e.g., 'grok-4.3', 'grok-4.3-latest')
#' \item Kimi: kimi-*, moonshot* (e.g., 'kimi-k2.7', 'moonshot-v1-8k')
#' \item OpenRouter: Any model with '/' in the name (e.g., 'openai/gpt-5.5', 'anthropic/claude-opus-4.7')
#' }
#' @export
Expand Down
111 changes: 111 additions & 0 deletions R/R/kimi_processor.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#' Kimi API Processor
#'
#' Concrete implementation of BaseAPIProcessor for Kimi (Moonshot AI) models.
#' Kimi exposes an Anthropic-compatible Messages API: it uses the same
#' `/v1/messages` endpoint, `x-api-key` + `anthropic-version` headers, and
#' returns content under `content[[1]]$text`.
#'
#' @export
KimiProcessor <- R6::R6Class("KimiProcessor",
inherit = BaseAPIProcessor,

public = list(
#' @description
#' Initialize Kimi processor
#' @param base_url Optional custom API endpoint
initialize = function(base_url = NULL) {
super$initialize("kimi", base_url)
},

#' @description
#' Get default Kimi API URL
#
get_default_api_url = function() {
return("https://api.kimi.com/coding/v1/messages")
},

#' @description
#' Make API call to Kimi
#' @param chunk_content Prompt text to send
#' @param model Model identifier
#' @param api_key Kimi API key
make_api_call = function(chunk_content, model, api_key) {
# Prepare request body (Anthropic Messages format)
body <- list(
model = model,
max_tokens = 4096,
messages = list(
list(
role = "user",
content = chunk_content
)
)
)

self$logger$debug("Sending API request to Kimi",
list(model = model, provider = self$provider_name))

# Make the API request
response <- httr::POST(
url = self$get_api_url(),
httr::add_headers(
"x-api-key" = api_key,
"anthropic-version" = "2023-06-01",
"content-type" = "application/json"
),
body = body,
encode = "json",
httr::timeout(30)
)

private$stop_for_http_error(response, model, "Kimi")

return(response)
},

#' @description
#' Extract response content from Kimi API response
#' @param response HTTP response object
#' @param model Model identifier
extract_response_content = function(response, model) {
self$logger$debug("Parsing Kimi API response",
list(provider = self$provider_name, model = model))

# Parse the response
content <- httr::content(response, "parsed")

# Check if response has the expected structure (Anthropic format)
if (is.null(content) || is.null(content$content) || length(content$content) == 0 ||
is.null(content$content[[1]]$text)) {

self$logger$error("Unexpected response format from Kimi API",
list(provider = self$provider_name,
model = model,
content_structure = names(content),
content_available = !is.null(content$content),
content_count = if(!is.null(content$content)) length(content$content) else 0))

stop("Unexpected response format from Kimi API")
}

# Extract the response content
response_content <- content$content[[1]]$text

return(response_content)
},

#' @description
#' Extract normalized Kimi token usage
#' @param response HTTP response object
extract_usage = function(response) {
private$extract_usage_fields(
response,
prompt_field = "input_tokens",
completion_field = "output_tokens",
total_field = NULL,
cost_field = NULL,
derive_total = TRUE
)
}
)
)
7 changes: 7 additions & 0 deletions R/R/process_kimi.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#' Process request using Kimi models
#'
#' @keywords internal
process_kimi <- function(prompt, model, api_key, base_url = NULL) {
processor <- KimiProcessor$new(base_url = base_url)
return(processor$process_request(prompt, model, api_key))
}
140 changes: 140 additions & 0 deletions R/man/KimiProcessor.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions R/man/get_provider.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions R/man/process_kimi.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions python/mllmcelltype/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ class ProviderConfig:
default_api_url="https://api.x.ai/v1/chat/completions",
model_prefixes=("grok-",),
),
"kimi": ProviderConfig(
default_model="kimi-k2.7",
api_key_env_var="KIMI_API_KEY",
default_api_url="https://api.kimi.com/coding/v1/messages",
model_prefixes=("kimi-", "moonshot"),
),
"openrouter": ProviderConfig(
default_model="openai/gpt-5.5",
api_key_env_var="OPENROUTER_API_KEY",
Expand Down
2 changes: 2 additions & 0 deletions python/mllmcelltype/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
process_deepseek,
process_gemini,
process_grok,
process_kimi,
process_minimax,
process_openai,
process_openrouter,
Expand All @@ -25,6 +26,7 @@
"zhipu": process_zhipu,
"minimax": process_minimax,
"grok": process_grok,
"kimi": process_kimi,
"openrouter": process_openrouter,
}

Expand Down
2 changes: 2 additions & 0 deletions python/mllmcelltype/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .deepseek import process_deepseek
from .gemini import extract_gemini_usage, process_gemini
from .grok import process_grok
from .kimi import process_kimi
from .minimax import process_minimax
from .openai import process_openai
from .openrouter import process_openrouter
Expand All @@ -21,6 +22,7 @@
"process_deepseek",
"process_gemini",
"process_grok",
"process_kimi",
"process_minimax",
"process_openai",
"process_openrouter",
Expand Down
Loading