feat: OpenAI, Anthropic, and native-Ollama providers + media pipeline
Phase 3: - provider/openai: Chat Completions for OpenAI + compat endpoints (SSE streaming with by-index tool-call assembly, response_format json_schema, legacy max_tokens option, reasoning_effort) - provider/anthropic: Messages API (tool_use/tool_result, GA structured output via output_config.format, full SSE event parser, 529 transient) - provider/ollama: one native /api/chat client behind the ollama, ollama-cloud, and foreman built-ins (presets; NDJSON streaming tolerant of foreman's buffered single-object responses; object tool arguments; format-schema structured output; think mapping) - media/: capability normalization (sniff, downscale, transcode, byte ladder, ErrUnsupported), wired into the chain executor per target with penalty-free advance past incapable elements - registry: real provider + scheme wiring, WithHTTPClient option, required env-foreman TLS chat round-trip test - ADR-0009 multimodal strategy, ADR-0010 tools/structured mapping; README matrix + CLAUDE.md synced Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# ADR-0010: Tools and structured output — one canonical shape, native mappings
|
||||
|
||||
**Status:** Accepted — 2026-06-10
|
||||
|
||||
## Context
|
||||
|
||||
Tool calling and schema-constrained output exist on every target but with
|
||||
different wire shapes (verified against current docs, June 2026; shapes
|
||||
recorded in each provider's package doc). The canonical API must hide all
|
||||
of it.
|
||||
|
||||
## Decision
|
||||
|
||||
Canonical: `Tool{Name, Description, Parameters (JSON Schema), Handler}`;
|
||||
`Response.ToolCalls[]{ID, Name, Arguments json.RawMessage}`; results return
|
||||
as `ToolResultsMessage(ToolResult{ID, Name, Content, IsError})`. Structured
|
||||
output via `WithSchema(schema, name)`. Per-provider mapping:
|
||||
|
||||
| Concern | OpenAI(+compat) | Anthropic(+compat) | Ollama/foreman | Google (Phase 4) |
|
||||
|---|---|---|---|---|
|
||||
| Tool def | `tools[].function{name,description,parameters}` | `tools[]{name,description,input_schema}` | `tools[].function` | `FunctionDeclaration.ParametersJsonSchema` |
|
||||
| Call args | JSON **string** → RawMessage | `tool_use.input` object | `arguments` **object** | `FunctionCall.Args` map |
|
||||
| Results | one `role:tool` msg per result (`tool_call_id`) | one **user** msg, `tool_result` blocks (`is_error` native) | `role:tool` + `tool_name` | `FunctionResponse` parts |
|
||||
| IsError | `"ERROR: "` content prefix | `is_error: true` | `"ERROR: "` prefix | response payload field |
|
||||
| Forced choice | `tool_choice` string / named object | `{"type":"any"/"tool"/"none"}` | none → drop tools; others best-effort ignored | `FunctionCallingConfig` modes |
|
||||
| Structured | `response_format json_schema` (no strict flag) | `output_config.format json_schema` (GA mechanism) | `format: <schema>` | `ResponseJsonSchema` + JSON MIME |
|
||||
|
||||
Cross-cutting decisions:
|
||||
|
||||
- **Missing call ids are synthesized** (`call_<n>`) — Ollama and some
|
||||
compat servers omit them; the agent loop needs ids to match results.
|
||||
- **Streaming buffers tool-call arguments to completion** (ADR-0002):
|
||||
OpenAI fragments accumulate by index, Anthropic `input_json_delta`
|
||||
fragments accumulate per block; consumers only ever see parseable calls.
|
||||
- **No strict-mode flag is sent** to OpenAI: strict mode imposes schema
|
||||
constraints (every property required, additionalProperties:false) that
|
||||
caller-supplied schemas may not satisfy. The `Generate[T]` reflector
|
||||
(Phase 5) emits strict-compatible schemas anyway.
|
||||
- `SchemaName` feeds providers that need a name (OpenAI; default
|
||||
"response"); others ignore it.
|
||||
- Tool handlers never panic the loop: `Toolbox.Execute`/`ExecuteTool`
|
||||
recover panics and JSON-encode results (ADR to agent loop, Phase 5).
|
||||
|
||||
## Consequences
|
||||
|
||||
- One test matrix per provider asserts the exact wire JSON both directions;
|
||||
drift is caught by httptest fixtures, not in production.
|
||||
- Ollama's missing tool_choice means "required" cannot be enforced there —
|
||||
documented in the README matrix rather than emulated.
|
||||
Reference in New Issue
Block a user