Skip to content
Open
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ Give it a model and a key and it goes. It speaks the OpenAI-compatible API by de
|---|---|
| OpenAI (default `gpt-5.5`) | `OPENAI_API_KEY=sk-...` |
| DeepSeek | `OPENAI_API_KEY=sk-... OPENAI_BASE_URL=https://api.deepseek.com CORECODER_MODEL=deepseek-chat` |
| MiniMax (global, OpenAI) | `OPENAI_API_KEY=sk-... OPENAI_BASE_URL=https://api.minimax.io/v1 CORECODER_MODEL=MiniMax-M3 CORECODER_MAX_CONTEXT=1000000` |
| MiniMax (China, OpenAI) | `OPENAI_API_KEY=sk-... OPENAI_BASE_URL=https://api.minimaxi.com/v1 CORECODER_MODEL=MiniMax-M3 CORECODER_MAX_CONTEXT=1000000` |
| MiniMax (global, Anthropic via LiteLLM) | `CORECODER_API_KEY=sk-... CORECODER_BASE_URL=https://api.minimax.io/anthropic CORECODER_MODEL=anthropic/MiniMax-M3 CORECODER_MAX_CONTEXT=1000000 CORECODER_PROVIDER=litellm` |
| MiniMax (China, Anthropic via LiteLLM) | `CORECODER_API_KEY=sk-... CORECODER_BASE_URL=https://api.minimaxi.com/anthropic CORECODER_MODEL=anthropic/MiniMax-M3 CORECODER_MAX_CONTEXT=1000000 CORECODER_PROVIDER=litellm` |
| Local Ollama | `OPENAI_API_KEY=ollama OPENAI_BASE_URL=http://localhost:11434/v1 CORECODER_MODEL=qwen2.5-coder` |

MiniMax-M2.7 is available through the same four endpoints. Replace `MiniMax-M3` with `MiniMax-M2.7` and set `CORECODER_MAX_CONTEXT=204800`; keep the `anthropic/` prefix when using LiteLLM. The Anthropic-compatible examples require `pip install "corecoder[litellm]"`.

Kimi, Qwen and the like are the same two variables; for providers that don't even offer an OpenAI-compatible endpoint, the optional LiteLLM backend (`pip install "corecoder[litellm]"`) routes to a hundred-plus of them. The third essay goes into this in detail. The key can be `export`ed directly or dropped into a `.env` at the project root, which is loaded on startup. Then:

```bash
Expand Down
6 changes: 6 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,14 @@ pip install -e .
|---|---|
| OpenAI(默认 `gpt-5.5`) | `OPENAI_API_KEY=sk-...` |
| DeepSeek | `OPENAI_API_KEY=sk-... OPENAI_BASE_URL=https://api.deepseek.com CORECODER_MODEL=deepseek-chat` |
| MiniMax (global, OpenAI) | `OPENAI_API_KEY=sk-... OPENAI_BASE_URL=https://api.minimax.io/v1 CORECODER_MODEL=MiniMax-M3 CORECODER_MAX_CONTEXT=1000000` |
| MiniMax (China, OpenAI) | `OPENAI_API_KEY=sk-... OPENAI_BASE_URL=https://api.minimaxi.com/v1 CORECODER_MODEL=MiniMax-M3 CORECODER_MAX_CONTEXT=1000000` |
| MiniMax (global, Anthropic via LiteLLM) | `CORECODER_API_KEY=sk-... CORECODER_BASE_URL=https://api.minimax.io/anthropic CORECODER_MODEL=anthropic/MiniMax-M3 CORECODER_MAX_CONTEXT=1000000 CORECODER_PROVIDER=litellm` |
| MiniMax (China, Anthropic via LiteLLM) | `CORECODER_API_KEY=sk-... CORECODER_BASE_URL=https://api.minimaxi.com/anthropic CORECODER_MODEL=anthropic/MiniMax-M3 CORECODER_MAX_CONTEXT=1000000 CORECODER_PROVIDER=litellm` |
| 本地 Ollama | `OPENAI_API_KEY=ollama OPENAI_BASE_URL=http://localhost:11434/v1 CORECODER_MODEL=qwen2.5-coder` |

MiniMax-M2.7 is available through the same four endpoints. Replace `MiniMax-M3` with `MiniMax-M2.7` and set `CORECODER_MAX_CONTEXT=204800`; keep the `anthropic/` prefix when using LiteLLM. The Anthropic-compatible examples require `pip install "corecoder[litellm]"`.

Kimi、Qwen 这些同样是改这两个变量;连 OpenAI 兼容接口都不给的 provider,装上可选的 LiteLLM 后端(`pip install "corecoder[litellm]"`)能路由一百多家。第三篇文章把这块讲得更细。key 可以直接 `export`,也可以在项目根目录扔个 `.env`,启动时自动加载。然后:

```bash
Expand Down
60 changes: 47 additions & 13 deletions corecoder/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ def message(self) -> dict:
return msg


# pricing per million tokens: (input, output)
# pricing per million tokens: (input, output, optional cache read, optional cache write)
# sources: openai.com/api/pricing, api-docs.deepseek.com, platform.claude.com,
# platform.moonshot.ai, alibabacloud.com/help/en/model-studio
# platform.moonshot.ai, alibabacloud.com/help/en/model-studio, platform.minimax.io
_PRICING = {
# OpenAI - current flagships
"gpt-5.5": (5, 30),
Expand All @@ -78,9 +78,33 @@ def message(self) -> dict:
"qwen-max": (0.78, 3.9),
# Moonshot Kimi
"kimi-k2.5": (0.6, 3),
# MiniMax
"MiniMax-M3": (0.6, 2.4, 0.12, None),
"MiniMax-M2.7": (0.3, 1.2, 0.06, 0.375),
}


def _pricing_key(model: str) -> str:
"""Strip a LiteLLM provider prefix before looking up model pricing."""
return model.rsplit("/", 1)[-1]


def _cost_for_usage(
model: str,
prompt_tokens: int,
completion_tokens: int,
) -> float | None:
key = _pricing_key(model)
pricing = _PRICING.get(key)
if not pricing:
return None
input_rate, output_rate = pricing[:2]
return (
prompt_tokens * input_rate / 1_000_000
+ completion_tokens * output_rate / 1_000_000
)


class ScriptedLLM:
"""Deterministic offline LLM for demos and smoke tests.

Expand Down Expand Up @@ -119,18 +143,29 @@ def __init__(
self.extra = kwargs # temperature, max_tokens, etc.
self.total_prompt_tokens = 0
self.total_completion_tokens = 0
self._estimated_cost = _cost_for_usage(model, 0, 0)

@property
def estimated_cost(self) -> float | None:
"""Rough cost estimate in USD. Returns None if model not in pricing table."""
pricing = _PRICING.get(self.model)
if not pricing:
return None
input_rate, output_rate = pricing
return (
self.total_prompt_tokens * input_rate / 1_000_000
+ self.total_completion_tokens * output_rate / 1_000_000
if "_estimated_cost" in self.__dict__:
return self._estimated_cost
return _cost_for_usage(
self.model,
self.total_prompt_tokens,
self.total_completion_tokens,
)

def _record_usage(self, prompt_tokens: int, completion_tokens: int):
self.total_prompt_tokens += prompt_tokens
self.total_completion_tokens += completion_tokens
request_cost = _cost_for_usage(
self.model,
prompt_tokens,
completion_tokens,
)
if request_cost is not None:
self._estimated_cost = (self._estimated_cost or 0.0) + request_cost

def chat(
self,
Expand Down Expand Up @@ -205,8 +240,7 @@ def chat(
args = {}
parsed.append(ToolCall(id=raw["id"], name=raw["name"], arguments=args))

self.total_prompt_tokens += prompt_tok
self.total_completion_tokens += completion_tok
self._record_usage(prompt_tok, completion_tok)

return LLMResponse(
content="".join(content_parts),
Expand Down Expand Up @@ -261,6 +295,7 @@ def __init__(
self.extra = kwargs
self.total_prompt_tokens = 0
self.total_completion_tokens = 0
self._estimated_cost = _cost_for_usage(model, 0, 0)

def chat(
self,
Expand Down Expand Up @@ -325,8 +360,7 @@ def chat(
args = {}
parsed.append(ToolCall(id=raw["id"], name=raw["name"], arguments=args))

self.total_prompt_tokens += prompt_tok
self.total_completion_tokens += completion_tok
self._record_usage(prompt_tok, completion_tok)

return LLMResponse(
content="".join(content_parts),
Expand Down
35 changes: 35 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,41 @@ def test_cost_estimation_unknown_model():
assert llm.estimated_cost is None


def test_minimax_pricing_registry_preserves_official_rates():
from corecoder.llm import _PRICING

assert _PRICING["MiniMax-M3"] == (0.6, 2.4, 0.12, None)
assert _PRICING["MiniMax-M2.7"] == (0.3, 1.2, 0.06, 0.375)


def test_minimax_m3_cost_estimation_uses_flat_pricing():
from corecoder.llm import LLM

for model in ("MiniMax-M3", "anthropic/MiniMax-M3"):
llm = LLM.__new__(LLM)
llm.model = model
llm.total_prompt_tokens = 500_000
llm.total_completion_tokens = 100_000
assert llm.estimated_cost == 0.54


def test_minimax_m3_cost_tracks_each_request_separately():
from corecoder.llm import LLM

llm = LLM.__new__(LLM)
llm.model = "MiniMax-M3"
llm.extra = {}
llm.total_prompt_tokens = 0
llm.total_completion_tokens = 0
llm._estimated_cost = 0.0

llm._record_usage(400_000, 100_000)
llm._record_usage(400_000, 100_000)

assert llm.total_prompt_tokens == 800_000
assert llm.estimated_cost == 0.96


# --- Changed files tracking ---

def test_edit_tracks_changed_files(tmp_path):
Expand Down