Skip to content

M1 PR-5: generation adapters — Ollama structured outputs + Anthropic#3

Merged
ryandmonk merged 1 commit into
feat/surface-gatesfrom
feat/adapters
Jul 2, 2026
Merged

M1 PR-5: generation adapters — Ollama structured outputs + Anthropic#3
ryandmonk merged 1 commit into
feat/surface-gatesfrom
feat/adapters

Conversation

@ryandmonk

Copy link
Copy Markdown
Contributor

What this is

PR-5 of Milestone 1 (stacked on #2). The generation-adapter seam (ADR-9 as amended):

  • Stateless GenerationAdapter interface — one attempt per call; the repair loop owns conversation state. Model identity is configuration: constructors require an explicit model id and a source-scan test asserts no model name exists in adapter code.
  • OllamaAdapter/api/chat with format = the generation schema. Non-JSON output raises a typed AdapterOutputError (never a silent retry): the S0 spike found the mlx engine silently ignoring format, so adapters guarantee only parseability — conformance is judged by gates S1/S2 over the artifact.
  • AnthropicAdapter — official SDK, output_config.format (json_schema); the generation schema is compatible by construction (depth-unrolled/non-recursive, closed objects). No sampling params sent. refusal and max_tokens stop reasons surface as typed errors.
  • scripts/smoke-ollama.ts — live, non-CI: one real generation through the compiled context + S1–S3 lint.

Acceptance (in CI on this PR)

npm test   # offline/deterministic via injected fetch fixtures: parsed results, the compiled
           # generation schema round-trips VERBATIM into the request body, typed failures
           # (non-JSON / HTTP error / empty / refusal / truncation), no-default-model scan,
           # model-ref parsing incl. colon-bearing ollama tags

Hand-review focus

src/adapters/types.ts — the GenerationAdapter / GenerateRequest / GenerateResult interface (M1's third public interface).

ADRs: 9 (with 2/3 context).

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a stateless generation-adapter layer (ADR-9 seam) to unify model-provider calls behind a single GenerationAdapter interface, adding concrete adapters for Ollama (local structured outputs) and Anthropic (SDK structured outputs), along with offline/deterministic fixture-based tests and a non-CI Ollama smoke script.

Changes:

  • Add GenerationAdapter / request+result types, typed AdapterOutputError, and shared parsing/helpers.
  • Implement OllamaAdapter (/api/chat with format) and AnthropicAdapter (SDK output_config.format: json_schema) with typed failure surfacing.
  • Add deterministic adapter tests plus a live smoke:ollama script; add @anthropic-ai/sdk dependency.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/adapters/types.ts Defines the adapter seam types, typed adapter errors, JSON parsing helper, and --model ref parsing.
src/adapters/ollama.ts Implements Ollama /api/chat adapter using structured outputs and returns parsed JSON + meta/usage.
src/adapters/anthropic.ts Implements Anthropic SDK adapter using JSON-schema output formatting and typed stop-reason errors.
src/adapters/index.ts Re-exports adapter APIs and provides adapterFor(--model ...) factory.
src/adapters/adapters.test.ts Adds offline, deterministic fixture tests covering schema round-trip + typed failure cases + model identity rules.
scripts/smoke-ollama.ts Adds a live (non-CI) Ollama smoke generator and runs S1–S3 surface linting on the artifact.
package.json Adds smoke:ollama script and @anthropic-ai/sdk dependency.
package-lock.json Locks @anthropic-ai/sdk and its transitive dependencies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/adapters/types.ts
throw new AdapterOutputError(
adapterId,
"model output is not valid JSON — constrained decoding was not applied or failed (see docs/spike-structured-outputs.md: some engines silently ignore output schemas)",
raw,
Comment thread src/adapters/anthropic.ts
.join("");
if (raw === "") throw new AdapterOutputError(this.id, "empty model output");
if (message.stop_reason === "max_tokens") {
throw new AdapterOutputError(this.id, "output truncated (stop_reason: max_tokens) — raise params.maxTokens", raw);
Comment thread src/adapters/ollama.ts
Comment on lines +69 to +71
const data = (await response.json()) as OllamaChatResponse;
const raw = data.message?.content ?? "";
if (raw === "") throw new AdapterOutputError(this.id, "empty model output");
… PR-5)

- src/adapters/: stateless GenerationAdapter interface per ADR-9 (one attempt
  per call; the repair loop owns conversation state). Model identity is
  configuration: constructors require an explicit model id, no default model
  name exists in code — enforced by a source-scan test.
- OllamaAdapter: /api/chat with format = the generation schema. Non-JSON
  output raises AdapterOutputError (the S0 spike found the mlx engine
  silently ignoring format; gates S1/S2 judge conformance over the artifact).
- AnthropicAdapter: official SDK, output_config.format json_schema (the
  generation schema is compatible by construction: depth-unrolled, closed
  objects). No sampling params sent (removed on current models). refusal and
  max_tokens stop reasons surface as typed errors — never silently retried.
- Offline deterministic tests via injected fetch fixtures: parsed results,
  schema round-trip verbatim into the request body, typed failures.
- scripts/smoke-ollama.ts (live, non-CI): one real generation through the
  compiled context + S1-S3 lint. First live runs recorded in the spike
  addendum: the 8B model passes S1/S2 but fails S3 on every attempt
  (nested-interactive violations) even with rule steering in the prompt —
  live confirmation that the guarantee is the linter, not the prompt.

Verify: npm test (44 tests, offline); npm run smoke:ollama -- --model <tag>

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ryandmonk ryandmonk merged commit 538b6bb into feat/surface-gates Jul 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants