These workplans exist only in the retired local hub. Their random pre-ADR-007 identifiers are refused by C-06 as stale references, so they cannot be registered. Deriving from the canonical record id takes no identity from anything: central does not hold them and the old ids die with the cache. Records central already holds were deliberately left untouched. Refs CUST-WP-0068-T06 Assistant: claude-code Assistant-Model: opus Assistant-Process: 2583210@bnt-lap001 Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
309 lines
12 KiB
Markdown
309 lines
12 KiB
Markdown
---
|
||
id: LLM-WP-0007
|
||
type: workplan
|
||
title: "Kimi K3 default via OpenRouter + EUR spend reporting"
|
||
domain: agents
|
||
repo: llm-connect
|
||
status: finished
|
||
owner: codex
|
||
topic_slug: kimi-k3-default-spend-reporting
|
||
planning_priority: high
|
||
planning_order: 7
|
||
created: "2026-08-03"
|
||
updated: "2026-08-03"
|
||
depends_on_workplans:
|
||
- LLM-WP-0005
|
||
related_workplans:
|
||
- LLM-WP-0006
|
||
state_hub_workstream_id: "7878d7d1-b56f-5ac7-9203-e2ce44ac7c2e"
|
||
---
|
||
|
||
# LLM-WP-0007 — Kimi K3 default via OpenRouter + EUR spend reporting
|
||
|
||
**status:** finished
|
||
**owner:** codex
|
||
|
||
## Purpose
|
||
|
||
1. Make **MoonshotAI Kimi K3** (`moonshotai/kimi-k3` on OpenRouter) a first-class
|
||
basemodel option, and **promote it to the library default** once a live smoke
|
||
call succeeds (rate table, adapter default, config default, docs).
|
||
2. Ensure every path that uses **llm-connect from the command line** reports
|
||
**token usage and cost in euros** for the work it handled.
|
||
3. Make weekly spend easy to inspect:
|
||
- **Last week** Monday 00:00 → Sunday 24:00 (local operator timezone, default
|
||
Europe/Berlin)
|
||
- **Current week** Monday 00:00 → now
|
||
|
||
This workplan does **not** change activity-core runtime profiles or K8s deploy
|
||
defaults unless those profiles currently inherit the global OpenRouter default
|
||
without an explicit model — those stay explicit where they already are.
|
||
|
||
## Demand signal
|
||
|
||
Operator request (2026-08-03): no open workplans; next priority is evaluating
|
||
Kimi K3 as basemodel/default via OpenRouter, plus CLI cost transparency in
|
||
tokens and €, plus mon–sun / mon–now weekly spend views.
|
||
|
||
## Current repo state (as of 2026-08-03)
|
||
|
||
| Area | State |
|
||
|---|---|
|
||
| Active workplans | **None** — WP-0001…0006 + ADHOC-2026-06-02 all finished/completed |
|
||
| OpenRouter default model | `anthropic/claude-sonnet-4` (`openrouter.py`, `config.LLMConfig`) |
|
||
| Rate table | USD-only seed of nine models; **no** `moonshotai/kimi-k3` entry |
|
||
| Cost model | `estimate_cost` → `CostEstimate` with `cost_usd` only (`costs.py`) |
|
||
| CLI (`llm-connect`) | Registry tools only: `rates show`, `classes show`, `classes fit` — **no** prompt execute, **no** spend report |
|
||
| Usage persistence | Server optional audit JSON under `LLM_CONNECT_AUDIT_DIR`; quality ledger is for adaptive routing, not operator spend |
|
||
| BudgetTracker | Token-cap only; no EUR accounting |
|
||
|
||
OpenRouter live listing (checked 2026-08-03):
|
||
|
||
- Model id: `moonshotai/kimi-k3`
|
||
- Context: 1_048_576
|
||
- List price (USD per token → per 1k): **prompt $0.003 / 1k**, **completion $0.015 / 1k**
|
||
|
||
## Architecture sketch
|
||
|
||
```
|
||
llm_connect/
|
||
rates.py # + moonshotai/kimi-k3 rate; currency remains list-price USD
|
||
costs.py # CostEstimate gains EUR fields via FxRate
|
||
fx.py # NEW: USD→EUR conversion (config/env/snapshot; never invent FX)
|
||
usage.py # NEW: UsageEvent + UsageLedger (append-only JSONL)
|
||
openrouter.py # default model → moonshotai/kimi-k3 after T01 smoke
|
||
config.py # LLMConfig.model default aligned
|
||
cli.py # run | cost report | spend week
|
||
```
|
||
|
||
### Defaults (after smoke gate)
|
||
|
||
| Surface | New default |
|
||
|---|---|
|
||
| `OpenRouterAdapter` `_DEFAULT_MODEL` | `moonshotai/kimi-k3` |
|
||
| `LLMConfig.model` | `moonshotai/kimi-k3` |
|
||
| `create_adapter("openrouter")` with no model | inherits adapter default |
|
||
| Explicit profiles / activity-core triage model | **unchanged** (already pin a model) |
|
||
|
||
If T01 smoke **fails** (auth, model unavailable, empty content), keep Claude Sonnet
|
||
as default, ship Kimi as an **opt-in** model id in rates + docs, and mark T02
|
||
`wait` with a short note — do not force a broken default.
|
||
|
||
### Cost + EUR
|
||
|
||
- Keep rate table denominated in **USD** (OpenRouter list prices).
|
||
- Add `FxRate(usd_per_eur or eur_per_usd, source, captured_at)` with resolution:
|
||
1. Explicit arg / config
|
||
2. Env `LLM_CONNECT_EUR_PER_USD` (float: euros per one USD)
|
||
3. Bundled snapshot rate with `captured_at` (operator can refresh)
|
||
- Extend `CostEstimate` with optional `cost_eur`, `prompt_cost_eur`,
|
||
`completion_cost_eur`, `fx_source` — missing FX yields `cost_eur=None` with
|
||
explicit source (same philosophy as unknown models).
|
||
- Never treat missing rates or missing FX as zero cost.
|
||
|
||
### Usage ledger (spend history)
|
||
|
||
Append-only JSONL, separate from `QualityLedger` (quality is adaptive-routing
|
||
signal; spend is operator accounting).
|
||
|
||
```python
|
||
@dataclass(frozen=True)
|
||
class UsageEvent:
|
||
recorded_at: datetime # timezone-aware UTC
|
||
provider: str
|
||
model_id: str
|
||
prompt_tokens: int
|
||
completion_tokens: int
|
||
total_tokens: int
|
||
cost_usd: float | None
|
||
cost_eur: float | None
|
||
cost_source: str
|
||
fx_source: str | None
|
||
source: str # "cli" | "server" | "library"
|
||
tags: dict[str, Any] # optional request id, profile, etc.
|
||
```
|
||
|
||
Default ledger path resolution:
|
||
|
||
1. `--ledger` / explicit path
|
||
2. `LLM_CONNECT_USAGE_LEDGER` env
|
||
3. `~/.local/share/llm-connect/usage.jsonl` (XDG-style fallback)
|
||
|
||
Week windows use **Monday 00:00 local** → exclusive end. Local timezone default
|
||
`Europe/Berlin` (override: `--tz` / `LLM_CONNECT_TZ`).
|
||
|
||
### CLI surface
|
||
|
||
```bash
|
||
# One-shot prompt (records usage when ledger available)
|
||
llm-connect run "Summarise X" [--provider openrouter] [--model moonshotai/kimi-k3]
|
||
# stderr or footer: tokens in/out/total · € cost (and USD if useful)
|
||
|
||
# Weekly spend
|
||
llm-connect spend week # current week Mon → now
|
||
llm-connect spend week --last # previous Mon → Sun
|
||
llm-connect spend week --json
|
||
|
||
# Optional: show cost for a hypothetical token count
|
||
llm-connect cost estimate --model moonshotai/kimi-k3 --prompt-tokens N --completion-tokens M
|
||
```
|
||
|
||
Per-call reporting for `run`: always print tokens; print € when FX + rate known;
|
||
print clear `unknown` when not.
|
||
|
||
## Scope guardrails
|
||
|
||
**In scope**
|
||
|
||
- Kimi K3 rate entry + default flip after smoke
|
||
- EUR conversion on top of existing USD rate table
|
||
- Usage ledger + CLI run/spend/cost commands
|
||
- Unit tests with fixed FX and fake clock; no live network in default CI
|
||
- Contract doc updates under `contracts/functional/`
|
||
|
||
**Out of scope**
|
||
|
||
- Multi-currency rate tables beyond USD list + EUR display
|
||
- Billing reconciliation against OpenRouter invoices (estimate-based only)
|
||
- Changing activity-core K8s model pins or triage profile model ids
|
||
- Full TUI / web dashboard
|
||
- Automatic live FX fetch in CI (optional later; snapshot + env is enough)
|
||
|
||
## Tasks
|
||
|
||
```task
|
||
id: LLM-WP-0007-T01
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "ca5aabf6-6444-5710-8437-241c99177152"
|
||
```
|
||
|
||
**Validate Kimi K3 via OpenRouter.** Confirm `moonshotai/kimi-k3` is callable
|
||
with a real key when available (manual/operator smoke; CI uses mock). Add rate
|
||
table entry: prompt `$0.003` / completion `$0.015` per 1k USD (captured from
|
||
OpenRouter 2026-08-03). Document model id and pricing source in rates contract
|
||
or README providers table. Gate for T02.
|
||
|
||
```task
|
||
id: LLM-WP-0007-T02
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "cb0a4fb2-d008-51f8-b222-ee16dfdf16fe"
|
||
```
|
||
|
||
**Promote Kimi K3 to OpenRouter basemodel default** after T01 smoke passes.
|
||
Update `openrouter._DEFAULT_MODEL`, `LLMConfig.model`, any factory/docs that
|
||
advertise the default, and tests that hard-code `anthropic/claude-sonnet-4` as
|
||
the implicit default. Leave explicit profile models untouched. If smoke failed,
|
||
leave default unchanged and record the blocker in the task notes.
|
||
|
||
```task
|
||
id: LLM-WP-0007-T03
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "6fa5a4d7-e694-5a7b-b16b-4652062dd78d"
|
||
```
|
||
|
||
**USD→EUR cost conversion.** Add `fx` helper + extend `CostEstimate` with EUR
|
||
fields and `fx_source`. Resolution order: explicit → env → bundled snapshot.
|
||
Update `contracts/functional/costs.md`. Tests: known FX, missing FX, unknown
|
||
model still yields `cost_usd=None` / `cost_eur=None`.
|
||
|
||
```task
|
||
id: LLM-WP-0007-T04
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "0ac06271-5dea-5f4f-8ce6-b5db549a21df"
|
||
```
|
||
|
||
**Usage ledger.** Implement `UsageEvent` + append-only JSONL `UsageLedger`
|
||
(path locks patterned after `QualityLedger`). Helpers: `record(...)`,
|
||
`iter_events()`, `sum_range(start, end)` aggregating tokens + cost_usd/eur.
|
||
Default path via env / XDG. Tests for append, concurrent-safe append (best
|
||
effort flock), and range aggregation.
|
||
|
||
```task
|
||
id: LLM-WP-0007-T05
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "33443864-fdce-5b24-8a7f-00c0697a69a1"
|
||
```
|
||
|
||
**CLI `run` with per-call cost footer.** Extend `llm-connect` with a `run`
|
||
subcommand that creates an adapter, executes a prompt, prints content, and
|
||
reports tokens + € (and USD). Record a `UsageEvent` to the ledger. Wire cost
|
||
estimation from response usage + rate table + FX. Tests with mock adapter.
|
||
|
||
```task
|
||
id: LLM-WP-0007-T06
|
||
status: done
|
||
priority: high
|
||
state_hub_task_id: "23223ec1-5575-589d-a8b0-50df8d38bd3c"
|
||
```
|
||
|
||
**CLI weekly spend views.** `llm-connect spend week` and `spend week --last`
|
||
using Mon–Sun / Mon–now windows in `Europe/Berlin` (overridable). Human table +
|
||
`--json`. Empty ledger → zero totals, exit 0. Tests with synthetic events and
|
||
fixed timezone.
|
||
|
||
```task
|
||
id: LLM-WP-0007-T07
|
||
status: done
|
||
priority: medium
|
||
state_hub_task_id: "79dd15d9-b2ad-547b-a4e8-d80b26d342e4"
|
||
```
|
||
|
||
**Optional library/server recording.** When `LLM_CONNECT_USAGE_LEDGER` is set,
|
||
record usage from adapter post-call path and/or server `/execute` (source
|
||
`library` / `server`) so non-CLI traffic contributes to weekly spend. Keep
|
||
opt-in so library consumers are not surprised by home-dir writes.
|
||
|
||
```task
|
||
id: LLM-WP-0007-T08
|
||
status: done
|
||
priority: medium
|
||
state_hub_task_id: "4b835a23-048b-51ee-837d-48abd108f182"
|
||
```
|
||
|
||
**Docs + acceptance polish.** README: new default model, CLI examples for
|
||
`run` / `spend week`, env vars (`LLM_CONNECT_USAGE_LEDGER`,
|
||
`LLM_CONNECT_EUR_PER_USD`, `LLM_CONNECT_TZ`). Update `contracts/functional/`
|
||
as needed. Smoke script or Makefile target for optional live Kimi check.
|
||
|
||
## Acceptance
|
||
|
||
- [x] `moonshotai/kimi-k3` is in the default rate registry with documented USD rates
|
||
- [x] After successful smoke, OpenRouter/library default model is `moonshotai/kimi-k3`
|
||
- [x] `llm-connect run "..."` prints token counts and € cost (or explicit unknown)
|
||
- [x] Successful CLI runs append to the usage ledger
|
||
- [x] `llm-connect spend week` and `spend week --last` show tokens + € for the
|
||
correct Mon-based windows
|
||
- [x] Default CI tests pass offline (mock adapter, fixed FX, no live OpenRouter)
|
||
- [x] Missing rate or FX never reported as €0.00 without an `unknown` marker
|
||
|
||
## Risks / notes
|
||
|
||
- **Model id spelling:** operator said "kimi-k3"; OpenRouter id is
|
||
`moonshotai/kimi-k3` (not K2 variants). Stick to that id unless operator
|
||
renames the requirement.
|
||
- **FX staleness:** bundled EUR rate will drift; env override is the operator
|
||
escape hatch. Do not scrape FX APIs in the hot path for v1.
|
||
- **Default flip blast radius:** any consumer relying on implicit Sonnet will
|
||
change behaviour — document in README and release notes; profiles that pin
|
||
models are safe.
|
||
- **Estimate vs invoice:** ledger costs are list-price estimates, not OpenRouter
|
||
billed totals (discounts, cache reads, provider routing may differ).
|
||
|
||
## Implementation order
|
||
|
||
T01 → T02 (gated) in parallel with T03 → T04 → T05 → T06 → T07 → T08.
|
||
T03/T04 can start before T01; T02 must wait for T01 smoke outcome.
|
||
|
||
|
||
## Implementation notes (2026-08-03)
|
||
|
||
- Live OpenRouter smoke: `moonshotai/kimi-k3` returned content "ok" with usage tokens (Together provider). Note: short `max_tokens` can yield empty `content` while spending tokens on reasoning.
|
||
- Default OpenRouter / LLMConfig model flipped to `moonshotai/kimi-k3`.
|
||
- Added `fx.py`, EUR fields on `CostEstimate`, `usage.py` ledger, CLI `run` / `cost estimate` / `spend week`.
|
||
- Opt-in library/server recording via `LLM_CONNECT_USAGE_LEDGER`; CLI suppresses double-record.
|
||
- Full unit suite: 224 passed offline.
|