Commit graph

2 commits

Author SHA1 Message Date
Till JS
51f408755c fix(api/who): use /v1/chat/completions path for mana-llm
The who module's chat endpoint was returning 502 to the browser
because mana-api called /api/v1/chat/completions on mana-llm and
got 404 — mana-llm exposes the OpenAI-compatible /v1/chat/completions
path with no /api/ prefix.

This is the same bug research had until commit 63a91e36a fixed its
path. The chat module (apps/api/src/modules/chat/routes.ts) still
has the wrong path — flagged as a follow-up.

Diagnostic from inside the mana-api container:
  /v1/chat/completions       → 422 (right path, empty body)
  /api/v1/chat/completions   → 404 (wrong path)

mana-api log line that flagged it:
  who.llm_non_200 status:404

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 15:48:09 +02:00
Till JS
74b5808496 feat(api): who module — LLM character-guessing endpoint cluster
Server side of the who module. Three endpoints under /api/v1/who/*:

  POST /chat
    Hot path. Body: { gameId, characterId, message, history[] }.
    Looks up character by id (server-side only — clients never see
    personalities), builds a system prompt instructing the LLM to
    roleplay the figure WITHOUT revealing its name and to append
    [IDENTITY_REVEALED] when the player has guessed correctly,
    forwards to mana-llm. Response: { reply, identityRevealed,
    characterName? } — characterName only present on win.

    Same credit pattern as chat module: validateCredits + consume
    after the LLM call succeeds. Operation 'AI_WHO', cheap (0.1
    credit) for local models, 5 for cloud.

  POST /random
    Picks a random character from a deck and returns just the id +
    category + difficulty. Frontend uses this to start a new game
    without ever knowing the personality pool. Server-side
    randomness so a determined attacker can't predict picks.

  POST /guess
    Explicit "I think it's X" submission. Fallback path for when
    the LLM forgets to emit the sentinel even though the player
    clearly said the right name. Deterministic lowercase substring
    match against the canonical name (with diacritic stripping +
    last-name-only matching for unambiguous figures like "Tesla").

  GET /decks
    Public deck catalogue with counts and category labels. Zero
    sensitive data — never leaks names or personalities. Used by
    the picker UI on mount.

data/characters.ts holds 37 characters: the original 26 from
whopixels verbatim + 11 new for the antiquity / women / inventors
decks. Each entry is in one or more decks via a `decks` array, so
e.g. Marie Curie shows up in both `historical` and `women`. Adding
a new character is one entry.

The system prompt is the carefully-tested German prompt from the
original whopixels server.js — tells the LLM to respond in the
language the user writes, give subtle hints, never directly say
"I am X", and emit the sentinel only on a correct guess.

The explicit-guess matcher catches three patterns:
  1. Exact normalized match ("Marie Curie" === "marie curie")
  2. Last-name-only ("Curie" matches "Marie Curie")
  3. Guess-contains-name ("I think it's Marie Curie" → contains)

Closes Phase A.1 of docs/WHO_MODULE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 13:09:46 +02:00