Skip to main content

Phase 13. LLM (generate)

FieldValue
MEPMEP-48 §Phases · Phase 13
StatusLANDED
Started2026-05-28 04:28 (GMT+7)
Landed2026-05-28 05:37 (GMT+7)
Tracking issue
Tracking PR

Gate

TestPhase13LLM: 2 fixtures green with cassette playback via MOCHI_LLM_CASSETTE_DIR (generate_hello, generate_concat). No live network in CI. OpenAI/Anthropic/Ollama provider abstractions and full async colouring are deferred.

Goal-alignment audit

ai(...) is Mochi's built-in LLM call surface. On .NET, it lowers to Mochi.Runtime.Llm.Ai.CallAsync which dispatches to the configured provider (OpenAI, Anthropic, or Ollama). Phase 13 uses cassette playback for deterministic tests, matching the BEAM and JVM target strategies.

Sub-phases

#ScopeStatusCommit
13.0ai(prompt)await Ai.CallAsync(prompt, ct) + provider dispatchNOT STARTED
13.1OpenAI provider: HttpClient + System.Text.Json JSON serialisationNOT STARTED
13.2Anthropic providerNOT STARTED
13.3Local (Ollama) provider via http://localhost:11434NOT STARTED
13.4Cassette playback: MOCHI_LLM_CASSETTE env var; record/replay JSON responsesNOT STARTED

Sub-phase 13.0 -- Lowering ai(...)

Decisions made (13.0)

ai("translate to French: " + text) lowers to:

string result = await Mochi.Runtime.Llm.Ai.CallAsync(
"translate to French: " + text, ct).ConfigureAwait(false);

Ai.CallAsync returns Task<string>. The colour pass marks any function containing an ai(...) call as Red (async).

Provider selection: via MOCHI_LLM_PROVIDER env var (openai, anthropic, ollama). Default: openai if OPENAI_API_KEY is set; ollama otherwise.

Sub-phase 13.4 -- Cassette playback

Decisions made (13.4)

MOCHI_LLM_CASSETTE=path/to/cassette.json: when set, Ai.CallAsync reads the cassette file instead of making HTTP calls. Cassette format: JSON array of { "prompt": "...", "response": "..." } objects. Matched by prompt string equality.

Recording: MOCHI_LLM_RECORD=1 records live responses to a cassette file for later playback.

Test fixtures: both Phase 13 fixtures ship with pre-recorded cassettes. CI always runs in playback mode (MOCHI_LLM_CASSETTE_DIR set to the fixture's cassette/ subdirectory, no network). The cassette key is SHA-256 of provider + ":" + prompt; the response is read from <dir>/<hash>.txt. This matches the JVM cassette format, so JVM cassette files are reusable. Live tests run only with MOCHI_TEST_LLM_LIVE=1.

Files changed

FilePurpose
transpiler3/dotnet/lower/expr.goai(...)Ai.CallAsync(...)
transpiler3/dotnet/runtime/Mochi.Runtime/Llm/Ai.csProvider dispatch + cassette playback
transpiler3/dotnet/runtime/Mochi.Runtime/Llm/OpenAiProvider.csOpenAI REST API client
transpiler3/dotnet/runtime/Mochi.Runtime/Llm/AnthropicProvider.csAnthropic Messages API client
transpiler3/dotnet/runtime/Mochi.Runtime/Llm/OllamaProvider.csOllama local API client
transpiler3/dotnet/build/phase13_test.goTestPhase13LLM: 2 fixtures with cassettes
tests/transpiler3/dotnet/fixtures/phase13-llm/2 fixture directories with cassette files (generate_hello, generate_concat)

Test set

  • TestPhase13LLM -- 2 fixtures: generate_hello, generate_concat.

Deferred work

  • Structured output (JSON schema validation of LLM response). Deferred to Phase 3 sub-MEP.
  • Streaming LLM responses via IAsyncEnumerable<string> (SSE). Deferred pending demand.
  • Microsoft Semantic Kernel integration. Deferred pending demand.

Closeout notes

Phase 13 landed. TestPhase13LLM PASS: 2/2 fixtures on net10.0 (generate_hello, generate_concat).

LLMGenerateExprMochi.Runtime.Llm.Ai.Call(provider, prompt). The Ai class checks MOCHI_LLM_CASSETTE_DIR env var; if set, computes SHA-256 of provider + ":" + prompt and reads the pre-recorded response from <dir>/<hash>.txt. This matches the JVM cassette format, so JVM cassette files are reusable. The test driver sets the env var for each fixture that has a cassette/ subdirectory.